How to interpolate into a Julia "for" expression? - metaprogramming

I was writing a macro #vcomp (vector comprehension) based on Python's list comprehensions with a conditional clause to filter elements in a succinct way.
macro vcomp(comprehension::Expr, when::Symbol, condition)
comp_head, comp_args = comprehension.head, comprehension.args
comp_head ∉ [:comprehension, :typed_comprehension] && error("#vcomp not a comprehension")
when ≠ :when && error("#vcomp expected `when`, got: `$when`")
T = comp_head == :typed_comprehension ? comp_args[1] : nothing
if VERSION < v"0.5-"
element = comp_head == :comprehension ? comp_args[1] : comp_args[2]
sequence = comp_head == :comprehension ? comp_args[2] : comp_args[3]
else
element = comp_head == :comprehension ? comp_args[1].args[1] : comp_args[2].args[1]
sequence = comp_head == :comprehension ? comp_args[1].args[2] : comp_args[2].args[2]
end
result = T ≠ nothing ? :($T[]) : :([])
block = Expr(:let, Expr(:block,
Expr(:(=), :res, result),
Expr(:for, sequence,
Expr(:if, condition,
Expr(:call, :push!, :res, element))),
:res))
return esc(block)
end
Used like this:
julia> #vcomp Int[i^3 for i in 1:10] when i % 2 == 0
5-element Array{Int64,1}:
8
64
216
512
1000
Which expand to this:
julia> macroexpand(:(#vcomp Int[i^3 for i in 1:15] when i % 2 == 0))
:(let
res = Int[]
for i = 1:15
if i % 2 == 0
push!(res,i ^ 3)
end
end
res
end)
I was expecting to be able to write block like this:
block = quote
let
res = $result
for $sequence
if $condition
push!(res, $element)
end
end
res
end
end
Which gives the following error:
ERROR: syntax: invalid iteration specification
Instead of the way I came up with:
block = Expr(:let, Expr(:block,
Expr(:(=), :res, result),
Expr(:for, sequence,
Expr(:if, condition,
Expr(:call, :push!, :res, element))),
:res))
However I was able to do it using Expr(:for, ...) directly as shown above and as far as I understand this is a parser error (is this a bug?). I have also been unable to find examples of this kind of interpolation, this is what I've tried:
julia> ex₁ = :(i in 1:10)
:($(Expr(:in, :i, :(1:10))))
julia> ex₂ = :(i = 1:10)
:(i = 1:10)
julia> quote
for $ex₁
ERROR: syntax: invalid iteration specification
julia> quote
for $ex₂
ERROR: syntax: invalid iteration specification
Construct whole expression and inspect it:
julia> ex₃ = quote
for i in 1:10
print(i)
end
end
quote # none, line 2:
for i = 1:10 # none, line 3:
print(i)
end
end
julia> ex₃.args
2-element Array{Any,1}:
:( # none, line 2:)
:(for i = 1:10 # none, line 3:
print(i)
end)
julia> ex₃.args[2].args
2-element Array{Any,1}:
:(i = 1:10)
quote # none, line 3:
print(i)
end
julia> ex₃.args[2].args[1]
:(i = 1:10)
julia> ex₃.args[2].args[1] == ex₂ # what's the difference then?
true
This works but is less readable:
julia> ex₄ = Expr(:for, ex₁, :(print(i)))
:(for $(Expr(:in, :i, :(1:10)))
print(i)
end)
julia> ex₅ = Expr(:for, ex₂, :(print(i)))
:(for i = 1:10
print(i)
end)
julia> eval(ex₃)
12345678910
julia> eval(ex₄)
12345678910
julia> eval(ex₅)
12345678910
Is there a way I can use the more terse syntax instead? I find the current implementation difficult to read and reason about compared to what I was expecting to write.

First of all, I belive that comprehensions with guards are coming to Julia (in v0.5?).
To answer your question: The parser wants to be able to verify that its input is syntactically correct without looking into the actual value that is interpolated. Try eg
x, y = :i, :(1:10)
quote
for $x = $y
end
end
Now the parser can recognize the relevant parts of the syntax. (And you should get the same AST if you use for $x in $y instead.)

Related

Python3 translating to TI Basic Store function in for loop being weird (Fibonacci's sequence)

I just converted the Fibonacci's sequence from python3 to TI Basic but its acting kind of weird when I try to execute the function similar to "+=" operator to the iterator variable in python where I use the store function.
Here is my TI code below:
Input "Enter number of terms: ",N
0→A
1→B
0→I
If N≤0:Then
Disp "Please enter a positive integer"
End
If N=1:Then
Disp "Fibonacci sequence upto",N
Else:
Disp "Fibonacci sequence:"
For(I,0,N-1)
Disp A
A+B→C
B→A
C→B
I+1→I
End
End
where when I input 3, it gives me an output of:
0
1
rather than
0
1
1
The code works if I either remove or change this line:
I+1→I
to
I→I
Is there a reason why this causes the for loop to ignore one iteration instead of starting from this iteration?
Here is the corresponding python code:
#Fibonacci Sequence
N = int(input("Enter number of terms: "))
A= 0
B = 1
I = 0
if N <= 0:
print("Please enter a positive integer")
if N == 1:
print("Fibonacci sequence upto",N)
else:
print("Fibonacci sequence:")
for I in range(N):
print(A)
C = A + B
A = B
B = C
I += 1
(I know that the I += 1 is unnecessary but it just made me curious why it doesn't work in the TIBasic language)
Original python fibonacci sequence code:
https://www.programiz.com/python-programming/examples/fibonacci-sequence
The for loop in ti basic iterates i by doing i = i+1.
The for loop in python sets i to a value from a sequence generated by range.
in other words
the ti code is more similar to
for(let i=0; i<n; i++){
}
and the python script is more similar to
let n = [1,2,4,5, ... n]
for(let ni=0; ni<n.length; ni++){
let i=n[ni];
}
You can see how in the python example above, 'i' gets reset to a value from a list each iteration. where as in the ti example 'i' is used to keep track of the iteration state.

Complete eval_strfrac(s, base), so that it returns its floating-point value

something is wrong with my while loop inside my eval_strfrac(s, base=2) function. for 3.14 base 10 it is very close,for 100.101 base 2 it is way off.Thanks!
#TEST to validate
def is_valid_strfrac(s, base=2):
return all([is_valid_strdigit(c, base) for c in s if c != '.']) \
and (len([c for c in s if c == '.']) <= 1)
def eval_strfrac(s, base=2):
assert is_valid_strfrac(s, base), "'{}' contains invalid digits for a base-{} number.".format(s, base)
#
predot,postdot = s.split('.')
whole = eval_strint(predot,bse)
whole = int(predot,base)
postlist = [int(p) for p in postdot]
print(postlist)
i = 0
while i <= len(postlist):
yo = (postlist[i])*((float(base))**-(float(i + 1)))
yo += yo
i +=1
return float(whole) + float(yo)
#### Test 0: `eval_strfrac_test0`#####
def check_eval_strfrac(s, v_true, base=2, tol=1e-7):
v_you = eval_strfrac(s, base)
assert type(v_you) is float, "Your function did not return a `float` as instructed."
delta_v = v_you - v_true
msg = "[{}]_{{{}}} ~= {}: You computed {}, which differs by {}.".format(s, base, v_true,
v_you, delta_v)
print(msg)
assert abs(delta_v) <= tol, "Difference exceeds expected tolerance."
# Test cases from the video
check_eval_strfrac('3.14', 3.14, base=10)
check_eval_strfrac('100.101', 4.625, base=2)
check_eval_strfrac('11.0010001111', 3.1396484375, base=2)
# A hex test case
check_eval_strfrac('f.a', 15.625, base=16)
print("\n(Passed!)")
[3.14]_{10} ~= 3.14: You computed 3.2, which differs by 0.06000000000000005.
[100.101]_{2} ~= 4.625: You computed 5.0, which differs by 0.375.
In the while loop, yo = (postlist[i])*((float(base))**-(float(i + 1))) calculates the value of one digit. Then yo += yo doubles it. Instead, you should be adding the values of digits to an accumulating sum.
Two lines later, return float(whole) + float(yo) returns from the function from inside the loop, so only one iteration of the loop is performed. The return should be after and outside the loop (not indented with the code inside the loop).

Interpolating an Expression into an Expression

I want to build a constructor with keyword arguments inside of a macro, and the first keyword argument needs to be for an expression. I am having trouble putting that expression into the expression. Here's what I mean. Say I have a type
type Test
ex
end
which holds an expression. I want to make a constructor where origex = :(a * b) is the default from a keyword argument. I tried
#eval :(Test(ex=$origex) = Test(origex))
But if you look at the expression that makes:
Test(ex=a * b) = begin # console, line 1:
Test(origex)
end
you see that it won't work because the a*b needs to still be an expression. So I tried
#eval :(Test(ex=:($origex)) = Test(origex))
but this has the odd expression
Test(ex=$(Expr(:quote, :($(Expr(:$, :origex)))))) = begin # console, line 1:
Test(origex)
end
which also won't eval. Instead I need to get
Test(ex=:(a * b)) = begin # console, line 1:
Test(origex)
end
as the expression to eval, but I don't know how to get that expression into an expression.
I think the following is what you want. You seem to have had a few mistakes:
julia> type Test
ex::Expr
end
julia> orig_ex = :(a + b)
:(a + b)
julia> new_ex = Meta.quot(orig_ex)
:($(Expr(:quote, :(a + b))))
julia> code = :( Test(; ex=$new_ex) = Test(ex) )
:(Test(; ex=$(Expr(:quote, :(a + b)))) = begin # REPL[4], line 1:
Test(ex)
end)
julia> eval(code)
Test
julia> Test()
Test(:(a + b))

Evaluating a mathematical expression without eval() on Python3 [duplicate]

This question already has answers here:
Evaluating a mathematical expression in a string
(14 answers)
Closed 10 months ago.
I'm working on a "copy-paste calculator" that detects any mathematical expressions copied to the system clipboard, evaluates them and copies the answer to the clipboard ready to be pasted. However, while the code uses the eval()-function, I'm not terribly concerned considering the user normally knows what they are copying. That being said, I want to find a better way without giving the calculations a handicap (= eg. removing the ability to calculate multiplications or exponents).
Here's the important parts of my code:
#! python3
import pyperclip, time
parsedict = {"×": "*",
"÷": "/",
"^": "**"} # Get rid of anything that cannot be evaluated
def stringparse(string): # Remove whitespace and replace unevaluateable objects
a = string
a = a.replace(" ", "")
for i in a:
if i in parsedict.keys():
a = a.replace(i, parsedict[i])
print(a)
return a
def calculate(string):
parsed = stringparse(string)
ans = eval(parsed) # EVIL!!!
print(ans)
pyperclip.copy(str(ans))
def validcheck(string): # Check if the copied item is a math expression
proof = 0
for i in mathproof:
if i in string:
proof += 1
elif "http" in string: #TODO: Create a better way of passing non-math copies
proof = 0
break
if proof != 0:
calculate(string)
def init(): # Ensure previous copies have no effect
current = pyperclip.paste()
new = current
main(current, new)
def main(current, new):
while True:
new = pyperclip.paste()
if new != current:
validcheck(new)
current = new
pass
else:
time.sleep(1.0)
pass
if __name__ == "__main__":
init()
Q: What should I use instead of eval() to calculate the answer?
You should use ast.parse:
import ast
try:
tree = ast.parse(expression, mode='eval')
except SyntaxError:
return # not a Python expression
if not all(isinstance(node, (ast.Expression,
ast.UnaryOp, ast.unaryop,
ast.BinOp, ast.operator,
ast.Num)) for node in ast.walk(tree)):
return # not a mathematical expression (numbers and operators)
result = eval(compile(tree, filename='', mode='eval'))
Note that for simplicity this allows all the unary operators (+, -, ~, not) as well as the arithmetic and bitwise binary operators (+, -, *, /, %, // **, <<, >>, &, |, ^) but not the logical or comparison operators. If should be straightforward to refine or expand the allowed operators.
without using eval, you'd have to implement a parser, or use existing packages like simpleeval (I'm not the author, and there are others, but I have tested that one successfully)
In one line, plus import:
>>> from simpleeval import simpleeval
>>> simpleeval.simple_eval("(45 + -45) + 34")
34
>>> simpleeval.simple_eval("(45 - 22*2) + 34**2")
1157
now if I try to hack the calculator by trying to import a module:
>>> simpleeval.simple_eval("import os")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "K:\CODE\COTS\python\simpleeval\simpleeval.py", line 466, in simple_eval
return s.eval(expr)
File "K:\CODE\COTS\python\simpleeval\simpleeval.py", line 274, in eval
return self._eval(ast.parse(expr.strip()).body[0].value)
AttributeError: 'Import' object has no attribute 'value'
Caught! the cryptic error message comes from the fact that simpleeval can evaluate variables that you can optionally pass through a dictionary. Catch AttributeError exception to intercept wrongly formed expressions. No need for eval for that.
By native Python3: without using inbuilt function
input_string = '1+1-1*4+1'
result = 0
counter = -1
for ch in range(len(input_string)):
if counter == ch:
continue
if input_string[ch] in ['-', '+', '/', '*', '**']:
next_value = int(input_string[ch+1])
if input_string[ch] == '-':
result -= next_value
counter = ch+1
elif input_string[ch] == '+':
result += next_value
counter = ch+1
elif input_string[ch] == '*':
result *= next_value
counter = ch+1
elif input_string[ch] == '/':
result /= next_value
counter = ch+1
elif input_string[ch] == '**':
result **= next_value
counter = ch+1
else:
result = int(input_string[ch])
print(result)
Output : 
The original string is : '1+1-1*4+1'
The evaluated result is : 5

My python code give this type error ''IndentationError: expected an indented block".give any solution

#!/bin/python
for i in range(1000): #153
pre = i #pre=153
a =str (i) #a='153'
sum=0
for j in a:
k = int (j) #k=1
q = k*k*k
summ = summ+q
if (pre = summ):
print("Armstorng ",pre)
else
print("not Armstorng ",pre)
if (pre = summ):
That should be a double equals sign because it's a comparison between two values, not a variable assignment.
if (pre == summ):
Also, a couple of (potential) spelling errors: "Armstorng" and "summ". (Unless you intend "sum"/"summ" to be different variables.)

Resources