Reputation: 233
I'm trying to convert strings of the form
'[[2, 3.1], [4/5, 5/6]]'
to a 2d list of Fraction objects from the fractions module. In the particular case given above, the result should be
[[Fraction(2, 1), Fraction(31, 10)], [Fraction(4, 5), Fraction(5, 6)]]
The spacing in the string won't necessarily be as uniform as I've written it (no spaces, extra spaces, uneven numbers of spaces, etc).
I can do this in several steps by peeling off the outer brackets, splitting the string at the ']'
s, removing empty strings from the resulting list, removing any nondigit from the beginning of each string in the list in a while loop, splitting each of those strings at the comma, and finally converting the elements of the nested lists to Fractions.
But that's fairly cumbersome. Is there a better solution for this?
Upvotes: 3
Views: 2515
Reputation: 10190
import ast
import re
from fractions import Fraction
input_text = '[[2, 3.1], [1e100, -5/6]]'
transformed_text = ast.literal_eval(re.sub(r'([^,\[\]\s]+)', r'"\1"', input_text))
transformed_text = [[ Fraction(j) for j in i] for i in transformed_text ]
Fraction(...)
can handle many different input forms; see here https://docs.python.org/3/library/fractions.html#fractions.Fraction with the noteFraction
constructor now accepts float
and decimal.Decimal instances
.ast.literal_eval
not eval
Upvotes: 0
Reputation: 104712
You can use regular expressions to transform the numeric literals in your string into calls to Fraction
. After the transformation, you can call eval
on the result to get the appropriately nested list.
You don't need to handle the division operations in a special way, since they will work exactly as you want if we just convert both numerator and denominator into Fraction
objects (so 4/5
becomes Fraction("4")/Fraction("5")
, which evaluates to Fraction(4, 5)
).
The transformation should put quotation marks around the numbers you pass to Fraction
, as that prevents eval
from parsing numbers with decimal points into float
instances (which usually causes a loss of precision). Fraction
can do the parsing instead, from a string, and it won't lose any precision since all finite-length decimal numbers have an exact Fraction
representation.
Code:
import re
from fractions import Fraction
input_text = '[[2, 3.1], [4/5, 5/6]]'
transformed_text = re.sub(r'([\d.]+)', r'Fraction("\1")', input_text)
results = eval(transformed_text)
print(results) # [[Fraction(2, 1), Fraction(31, 10)], [Fraction(4, 5), Fraction(5, 6)]]
Upvotes: 3
Reputation: 5286
I came up with a nifty code snippet that can help you out with it:
from fractions import Fraction
input_string = '[[2, 3.1], [4/5, 5/6]]'
parsed_input = [item for item in ''.join((char if char in '0123456789./' else '-') for char in input_string).split('-') if item]
output_array = [Fraction(i).limit_denominator(10) for i in parsed_input]
I hope this helps. Please use the comments section incase of any ambiguities.
Upvotes: 1