[topic] 分数の発見
Posted feedbacks - Nested
Flatten Hiddenこの場合はあまり役に立ってませんが、分数クラスを定義してみました。 こんな感じでどうでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Sample176 {
public static List<Rational> rationalizes(final double d) {
final Map<Rational, Double> map = new HashMap<Rational, Double>();
for (int index = 1; index < 10; index++) {
int nume = (int) (d * index);
Rational r1 = new Rational(nume, index);
Rational r2 = new Rational(nume + 1, index);
if ((r2.doubleValue() - d) - (d - r1.doubleValue()) > 0) {
map.put(r1, Math.abs(r1.doubleValue() - d));
} else {
map.put(r2, Math.abs(r2.doubleValue() - d));
}
}
List<Rational> result = new ArrayList<Rational>(map.keySet());
Collections.sort(result, new Comparator<Rational>() {
public int compare(Rational o1, Rational o2) {
return Double.compare(map.get(o1), map.get(o2));
}
});
return result;
}
public static void main(String[] args) {
System.out.println(rationalizes(1.732051));
System.out.println(rationalizes(3.141593));
System.out.println(rationalizes((double) 1920 / 1080));
}
}
class Rational extends Number {
public final int numerator;
public final int denominator;
public Rational(int p, int q) {
int nume = p;
int deno = q;
if (deno == 0) {
throw new IllegalArgumentException("#N/A");
} else if (nume == 0) {
deno = 1;
} else if (deno < 0) {
nume *= -1;
deno *= -1;
}
int r = gcd(nume, deno);
numerator = nume / r;
denominator = deno / r;
}
private int gcd(int n, int m) {
int p = n;
int q = m;
int r = p % q;
while (r > 0) {
p = q;
q = r;
r = p % q;
}
return q;
}
@Override
public int intValue() {
return numerator / denominator;
}
@Override
public long longValue() {
return this.intValue();
}
@Override
public float floatValue() {
return (float) this.doubleValue();
}
@Override
public double doubleValue() {
return (double) numerator / denominator;
}
@Override
public int hashCode() {
return numerator + denominator * 7;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Rational)) return false;
Rational other = (Rational) obj;
return this.denominator == other.denominator
&& this.numerator == other.numerator;
}
@Override
public String toString() {
return String.format("%d/%d", numerator, denominator);
}
}
|
表示が不完全だけど、リストを取り出すだけならば >(rationalize-list 1.732051) (12/7 7/4 16/9 5/3 9/5 3/2 2) 分数の時が不完全な表示になります。 >(print-rationalize-list 1920/1080 ) 16/9 => 16/9 9/5 7/4 11/6 12/7 5/3 2/1 NIL 単にニュートン法の応用です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | (defun check (given fraction min max)
(let ((center (floor (/ (+ min max) 2))))
(if (or (equal center min) (equal center max))
(/ (check-near given fraction (cons min max)) fraction)
(if (> (* given fraction) center)
(check given fraction center max)
(check given fraction min center)))))
(defun check-near (given fraction list)
(if (> (- given (/ (car list) fraction))
(- (/ (cdr list) fraction) given))
(cdr list)
(car list)))
(defun rationalize-list (given)
(let (list)
(loop for i from 1 to 9 do
(let ((ans (check given i 0 (* (1+ i) given))))
(and (not (find ans list))
(push ans list))))
(sort list
#'(lambda(x y)(< (abs (- x given))
(abs (- given y)))))))
(defun print-rationalize-list (given)
(let ((list (rationalize-list given)))
(format t "~s => " given)
(loop for v in list do
(multiple-value-bind (f r) (floor v)
(if (zerop r)
(format t "~d/1 " v)
(format t "~d " v))))
(format t "~%")))
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | def gcd(m, n):
assert m > 0
assert n > 0
while n:
m, n = n, m % n
assert m > 0
return m
class Rational:
def __init__(self, b, c):
g = gcd(b, c)
self.b = b // g
self.c = c // g
def __repr__(self):
return "%d/%d" % (self.b, self.c)
def __cmp__(self, that):
return self.b * that.c - self.c * that.b
def value(self):
return float(self.b) / self.c
def rationalize(a):
rationals = []
for c in xrange(1, 10):
b = int(a * c + 0.5)
r = Rational(b, c)
if r.b == b:
rationals.append(r)
rationals.sort(key=lambda r: abs(a - r.value()))
return rationals
def solve(a):
print "a = %f" % a, " ".join(repr(r) for r in rationalize(a))
def main():
solve(1.732051)
solve(3.141593)
solve(1920.0 / 1080)
if __name__ == '__main__':
main()
|
boost::math::gcd で既約分数のチェックは楽できました。
それ以外は普通?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | #include <iostream>
#include <vector>
#include <functional>
#include <cmath>
#include <iterator>
#include <string>
#include <stdexcept>
#include <boost/math/common_factor_rt.hpp>
#include <boost/lexical_cast.hpp>
class rational_t
{
int n_, d_;
double r_;
public:
rational_t(int numerator, int denominator)
: n_(numerator), d_(denominator),
r_(static_cast<double>(n_)/static_cast<double>(d_)) {}
double real() const
{ return r_; }
bool isnan() const
{ return std::isnan(r_); }
int numerator() const
{ return n_; }
int denominator() const
{ return d_; }
};
std::ostream&
operator<<(std::ostream& os, const rational_t& r)
{
return (os << r.numerator() << "/" << r.denominator());
}
class compare_near_to
: public std::binary_function<bool, rational_t, rational_t>
{
double g_;
public:
compare_near_to(double goal) : g_(goal) {}
bool operator()(
const rational_t& lhs,
const rational_t& rhs
)
{
return std::fabs(g_-lhs.real()) < std::fabs(g_-rhs.real());
}
};
rational_t
find_rational(
double goal,
int denominator
)
{
double min_diff = goal;
rational_t rat(0, denominator);
for ( int n = 1; ; ++n ) {
rational_t rat_(n, denominator);
double diff = std::fabs(goal - rat_.real());
if ( diff > min_diff ) break;
min_diff = diff;
rat = rat_;
}
if ( boost::math::gcd(rat.numerator(), rat.denominator()) != 1 )
return rational_t(0,0); // causes real is NaN
return rat;
}
int main(int c, char** v)
{
if ( c != 2 ) {
std::cout << v[0] << " <real number|rational number>\n";
return 0;
}
double goal;
try {
std::string val(v[1]);
std::string::size_type idx;
if ( (idx = val.find("/")) != std::string::npos )
goal = boost::lexical_cast<double>(val.substr(0,idx)) /
boost::lexical_cast<double>(val.substr(idx+1,val.length()-(idx+1)));
else
goal = boost::lexical_cast<double>(v[1]);
if ( goal <= 0.0 )
throw std::runtime_error("input number must be positive");
}
catch (const std::exception& e) {
std::cerr << e.what() << "\n";
return -1;
}
std::vector<rational_t> rationals;
for ( int i = 1; i < 10; ++i )
rationals.push_back(find_rational(goal, i));
rationals.erase(
std::remove_if(
rationals.begin(),
rationals.end(),
std::mem_fun_ref(&rational_t::isnan) ),
rationals.end());
std::sort(
rationals.begin(),
rationals.end(),
compare_near_to(goal) );
std::copy(
rationals.begin(),
rationals.end(),
std::ostream_iterator<rational_t>(std::cout, ", ") );
return 0;
}
|
同じ値の分数かどうかを「入力との差が等しいかどうか」で判定しているので、入力の値によっては誤差が出るかも……
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | def rationalizes value
# [入力との差, [分母, 分子]]からなる配列を作成する
candidates = (1..9).map do |moderator|
numerator = (value*moderator).round
difference = (value-numerator.quo(moderator)).abs
[difference, [moderator, numerator]]
end
# 入力との差、分母の順に比較してソートすることで、
# 入力との差が等しい場合には分母の小さい分数が先に並ぶ
# その上で、差が等しい場合は最初の分数のみを選択することで、
# 既約分数のみが返却される
latest_difference = nil
candidates.sort.inject([]) do |result, (difference, (moderator, numerator))|
unless difference == latest_difference
latest_difference = difference
result << "#{numerator}/#{moderator}"
end
result
end
end
p rationalizes(1.732051) #=> ["12/7", "7/4", "16/9", "5/3", "9/5", "3/2", "2/1"]
p rationalizes(3.141593) #=> ["22/7", "25/8", "19/6", "28/9", "16/5", "13/4", "3/1"]
p rationalizes(1920 / 1080.0) #=> ["16/9", "9/5", "7/4", "11/6", "12/7", "5/3", "2/1"]
|
「差が等しい」だと符号が違う値を同じ分数だとみなしてしまうので、値自体を比較に使用するように修正しました。
また、条件1には抵触しませんが「近い順に全て」という問いなので、「差が等しく」「分母が同じで」「分子が異なる」分数が検知できないのは間違いだと考えて修正しました。
※例えば0.7を入力した場合、分母が5の分数は4/5しか見つけられないが、3/5も4/5と同じだけ近いので検出されるべきではないか? という話
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | def rationalizes value
# [浮動小数点数, [分母, 分子]]からなる配列を作成する
candidates = (1..9).inject([]) do |result, moderator|
# 四捨五入すれば分母の近似値が求められるが、
# .5の場合は両方を使用する
numerator = value * moderator
if numerator%1.0 == 0.5
numerators = [numerator.floor, numerator.ceil]
else
numerators = [numerator.round]
end
numerators.each do |numerator|
float = numerator.quo(moderator)
result << [float, [moderator, numerator]]
end
result
end
# 入力との差、分母、分子の順に比較してソートすることで、
# 入力との差が等しい場合には分母、分子の小さい分数が先に並ぶ
candidates = candidates.sort_by do |float, fraction|
[(value-float).abs, fraction]
end
# 浮動小数点数が等しい場合は最初の分数のみを選択することで、
# 既約分数のみが返却される
latest_float = nil
candidates.inject([]) do |result, (float, (moderator, numerator))|
unless float == latest_float
latest_float = float
result << "#{numerator}/#{moderator}"
end
result
end
end
p rationalizes(1.732051) #=> ["12/7", "7/4", "16/9", "5/3", "9/5", "3/2", "2/1"]
p rationalizes(3.141593) #=> ["22/7", "25/8", "19/6", "28/9", "16/5", "13/4", "3/1"]
p rationalizes(1920 / 1080.0) #=> ["16/9", "9/5", "7/4", "11/6", "12/7", "5/3", "2/1"]
p rationalizes(0.7) #=> ["5/7", "2/3", "3/4", "3/5", "4/5", "1/2", "1/1"]
|
分数が組み込みなので簡単。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | (use srfi-1)
(define (sort-by xs cmp-fn key-fn)
(map car (sort (map (lambda (x) (cons x (key-fn x))) xs)
(lambda (a b) (cmp-fn (cdr a) (cdr b))))))
(define (main args)
(let ((n (string->number (cadr args))))
(for-each
print
(sort-by (delete-duplicates
(map (lambda (m)
(/ (inexact->exact (round (* m n))) m))
(iota 9 1)))
<
(lambda (x) (abs (- x n)))))))
|






gandalf #6278() Rating1/1=1.00
schemeにはrationalizeという手続きがありますが、これは結果を一つしか返しません。そして、複数の結果が欲しい場合も稀にあります。
そこで、非負の実数aが一つ与えられたときに、以下の条件を満たす分数b/cをaに近い順に全て表示する手続きを考えてみてください。条件は、 1. 分数b/cよりaに近い分数d/cは存在しない 2. 分数b/cは既約 3. cは1桁の整数 です。
例をいくつかあげます(あってると思うけど...)。
a = 1.732051 12/7 7/4 16/9 5/3 9/5 3/2 2/1
a = 3.141593 22/7 25/8 19/6 28/9 16/5 13/4 3/1
a = 1920 / 1080 16/9 9/5 7/4 11/6 12/7 5/3 2/1
[ reply ]