ラベル 数値計算 の投稿を表示しています。 すべての投稿を表示
ラベル 数値計算 の投稿を表示しています。 すべての投稿を表示

2016/06/28

[C#]要素をすべて掛ける(乗算する)

このエントリーをはてなブックマークに追加
要素をすべて乗算したかったんです。
すべて足し合わせるのは、Sumを使えばいいのですがLINQには乗算はありません。すべて掛けるなんて、あんまり使わないしね。
ExcelにはPRODUCT関数というのがあるらしく、値の積を計算するもののようです。このキーワードを知っていたら手早く探せたかも…。

Aggregateを使う

掛けるときはAggregateを使うとスマートでした。

int product = list.Aggregate((now, next) => now * next);

Aggregateコードは、LINQを使わずに展開すると下のコードと同じものです。

int next = 1;
foreach (int now in list) {
    next *= now;
}
int product = next;


めでたしめでたし。


サンプルソース


int[] list = { 1, 2, 3, 4 };

// すべて足す
int sum = list.Sum();
Console.WriteLine(sum); // 10

// すべて掛ける
int p = 1;
foreach (int val in list) {
    p *= val;
}
Console.WriteLine(p); // 24

// すべて掛ける
int product = list.Aggregate((now, next) => now * next);
Console.WriteLine(product); // 24

機会があったら覚えていくスタンス

さて、余談ですがAggregateはMSDNでは次のように説明されています。

Aggregate<TSource>(IEnumerable<TSource>, Func<TSource, TSource, TSource>)
シーケンスにアキュムレータ関数を適用します。

なんのこっちゃ、ぜんぜんわかりません。
LINQは使い方を知れば便利そうと解るけれど、インテリセンスやMSDNからの習得は難しいものもあるようです。
サンプルのコードがあって、こういう風に使うんだという一例があれば、なんとなくわかるし習得も容易ですね。

日頃よく使わないメソッドはやっぱりその挙動にも不安が残るものです。何度も検証しないと手放しで任せられないし、乗算のコードに展開したものを書いても、そんな目くじらを立てるほどのロスがあるわけでもありません。
意地でも新しいことを覚えないというスタンスはどうかと思いますが、片っ端から習得しようとしてもほとんどは出番がなく使わないままなのが現実です。
なので、機会があったら覚えていこうというくらいでいいかなと。

もちろんそのすべてを習得して自在に連携している人もいるでしょうし、それが理想モデルです。
けれど、自在に扱える人も右往左往しながら習得したはずですよ。

以上です。よきLINQライフを!

Calculate product with LINQ
http://stackoverflow.com/questions/252333/calculate-product-with-linq

2016/02/21

[js]サイズに合わせた画像の縮小

このエントリーをはてなブックマークに追加

画像を任意のサイズに縮小するときの計算方法について説明します。
ソースコードにはJavascriptを使っていますが、なるべく言語に依存しないようにするつもりです。

この手の計算は、寄り付かない人には、ものすごく苦手意識があるのではないかと想像します。
落ち着けば、大変な計算はぜんぜん出てきません。
また、理屈を簡潔に語るよりも、計算から感覚的にイメージがつかめるほうがよいと思ったので、わりとくどくど説明しています。


画像を任意のサイズに合わせる


画像を任意の矩形サイズに合わせたいとします。
やってくる画像のサイズはまちまちですが、なるべく画像の全容が見えるようにして、収まらない部分は切り落としてしまうことにします。


当然ですが、幅と高さにサイズを指定して、強引にギュッとゆがめてしまうのは画像にもよろしくありません(変倍というやつです)。
幅と高さは同じ比率(等倍)で収めるものとします。


横長画像か縦長画像かを判定する


画像によって、処理の仕方は2パターンあります。
横長の画像は左右が余るようにしたいですし、縦長の画像は上下を余るようにしたいですね。

具体的な数をもとに考えることができるように、例として、フラミンゴの画像とヒマワリの画像を使います。
それぞれの画像サイズはこちら。
高さ
Aフラミンゴの画像450px300px
Bヒマワリの画像400px400px
描画先の矩形サイズ100px80px

フラミンゴの画像とヒマワリの画像を、描画先の矩形サイズに合わせて縮小したいとします。

それぞれの縦横の比率を比較することで、どちらに合わせる画像かを判別させます。

フラミンゴの縦横比率(幅/高さ)= 450px / 300px = 3/2 = 1.5
ヒマワリの縦横比率(幅/高さ)= 400px / 400px = 1.0
描画先の矩形の縦横比率(幅/高さ)= 100px / 80px = 5/4 = 1.2

縦横比率の関係は、
フラミンゴ(1.5)  > 描画先の矩形(1.2) > ヒマワリ(1.0)
となっています。

描画先の矩形よりも縦横比率が大きいものは横長として扱えばよく、小さいものは縦長として扱えばよいことが解りますね。


切り出す画像と縮小率を計算する


必要となるのは、画像のどの部分を切り出して、どのくらい縮小すればいいかという2つです。
ひとまず横長画像について考えます。


先に縮小比を求める
どのくらい小さく(または大きく)すればいいのかを考えます。

横長の画像は左右に余りますので、画像の高さを、描画先の矩形の高さに合わせると考えると解りやすいですね。

縮小比 = 描画先の矩形の高さ / 画像の高さ
です。

※縮小と書いていますが、計算式には縮小と拡大の区別はありません。画像が小さくて、描画先の矩形のほうが大きいときは、縮小比が1よりも大きくなるだけですね。

切り取り領域を決める
画像から領域を切り取り、縮小することで、描画先の矩形に合わせます。
どの部分の領域を切り取ればいいかを求めます。

・高さ
高さはそのまま、画像の高さを描画先の矩形の高さにするだけです。

切り取る領域の高さ = 画像の高さ

・幅
幅について考えます。
切り取る領域の幅に縮小比を掛ければ描画先の矩形の幅になるはずなので、切り出し幅を求めるためには逆に縮小比で割ってあげればよいでしょう。

描画先の矩形の幅 = 切り取る領域の幅×縮小比
なので、
切り取る領域の幅 = 描画先の矩形の幅/縮小比
で計算できます。

・切り取りの開始位置
また切り取りの開始位置は横幅の画像は左右に余る分を、少しずらせばいいことが判ります。
画像の幅と切り取り幅の差分があまりの部分ですので、真ん中の部分を切り取るためには、差分を等分すればよいですね。


ということで、
切り取りの開始位置 = (画像の幅 - 切り取り幅) / 2
で計算できます。


縦長の画像のときの計算

縦長の画像も同じように計算しましょう。
縦長の画像の場合は、縮小比は幅の比率になります。

縮小比 = 描画先の矩形の幅 / 画像の幅

・幅
切り取る領域の幅 = 画像の幅

・高さ
切り取る領域の高さ = 描画先の矩形の高さ/縮小比

・開始位置
開始位置は、上下の位置を決めます。
縦の切り取りの開始位置 = (画像の幅 - 切り取る高さ) / 2
で計算できます。


ソースコード

サンプルとして、画像をロードし、幅250px、高さ100pxに合わせて縮小するソースコードを載せておきます。
なお、言語はJavascriptです。
html内にcanvasのidを持つcanvasタグが配置されているものとします。

var imageFile = "/Images/ヒマワリ.jpg";
var id = "canvas"
var canvasWidth = 250;
var canvasHeight = 100;

var canvas = document.getElementById(id);
canvas.width = canvasWidth;
canvas.height = canvasHeight;
var context = canvas.getContext('2d');

var image = document.createElement('img');
image.src = imageFile;

// 準備
image.onload = function () {
    var imageWidth = image.width;
    var imageHeight = image.height;

    // ①縮小比を計算
    var scale = 1.0;
    if (imageWidth / imageHeight > canvasWidth / canvasHeight) {
        scale = canvasHeight / imageHeight;
    } else {
        scale = canvasWidth / imageWidth;
    }

    // ②切り出し領域を計算
    var clipWidth = canvasWidth / scale;
    var clipHeight = canvasHeight / scale;
    var clipX = (imageWidth - clipWidth) / 2;
    var clipY = (imageHeight - clipHeight) / 2;
  
    // ③drawImageメソッドの呼び出し
    context.drawImage(image, clipX, clipY, clipWidth, clipHeight, 0, 0, canvasWidth, canvasHeight);

};


解説

計算に使っている変数をざっとリストアップしておきます。
imageWidth画像のオリジナルサイズの幅
imageHeight画像のオリジナルサイズの高さ
canvasWidth指定矩形の幅
canvasHeight指定矩形の高さ
scale縮小比
clipX切り取り領域の開始X座標
clipY切り取り領域の開始Y座標
clipWidth切り取り領域の幅
clipHeight切り取り領域の高さ

準備として、画像を読み込んでから処理されるようにしたいのでonloadイベントに記述しています。


①縮小比を計算

縦横比率を比較し、横長の画像と、縦長の画像に分けて縮小比率を計算しています。


完全に横道にそれますが、割り算は掛け算に比べてものすごく遅いです。
(imageWidth / imageHeight > canvasWidth / canvasHeight)
は、下のようにも書けます。
(imageWidth * canvasHeight > imageHeight * canvasWidth)
こちらのほうが高速です。


②切り出し領域を計算

画像の切り出し領域を計算しています。
横長でも縦長でも縮小比が解れば同じ計算式で導くことができます。


ただし不要な演算が増えているので注意。


③drawImageメソッドの呼び出し
canvasのdrawImageメソッドを呼び出しています。
drawImageは、canvasにイメージを描画するためのメソッドです。
drawImageは引数の多い9つあるのを使っています。

context.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)


sx,sy,sw,shで描画元からコピーする矩形を指定し、dx,dy,dw,dhで描画先の矩形を指定します。


以上で終わりですが、もう一つだけ足しておきます。


おまけ


上記は、できるだけ画像を矩形いっぱいに広げて収めるという要件でした。
そのため矩形よりもはみ出た領域は切り取られてしまいます。

替わりに、画像を切り取ることなく、画像の全体を矩形に表示したいとします。
こういうのもありそうな話です。

こういう感じですね。

横長画像か縦長画像かを判定するために縦横比率を比較しました。
この判定を反転させます。

上のソースコードでいうと、
if (imageWidth / imageHeight > canvasWidth / canvasHeight) {

if (imageWidth / imageHeight < canvasWidth / canvasHeight) {
にする。
するとあら不思議。

幅と高さのどちらを基準にサイズを合わせるか、というのを逆転させればよいので、これだけでできるのです。


おしまい。

2015/02/20

テンキーを回転させる計算式

このエントリーをはてなブックマークに追加
なんていうか練習問題です。頭の体操と思っていただければいいです。
テンキーというのは、1-9までの数字が、ちょうど5を中心に八方を示す配置ですね。下から123と三つずつ、三列並んでいるやつです。
これをぐるっと90度づつ回転させることを考えてみましょう。
if文を9つ(この場合はswitch文だけど)書いてもいいけど。任意のキーnを回転させたとして、計算式で求めてみよう。

右に90度回転させる

右に90度がっと回したとき。
こういうふうにしたい。

計算式
計算式は3掛けて、10でわったあまり。
n=(n * 3) % 10;

180度回転させる

180度回転。反転です。
という感じ。

計算式
計算式は、10引くだけ。
n=10-n;

左に90度回転させる

っていう。

計算式
右に90度回転させて、それを180度回転させると、左に90度回転させたことになる。
n=10-(n*3)%10;

左に90度回転させる(別解)

もしくは、右に回転するときは3掛けたので、その逆算、3でわってあげればいい。
ただし十のくらいが面倒で、3でわれない数字をわれるように十のくらいを補間する必要があります。2桁の数字は、桁のそれぞれの数字を足して3でわれるなら、元の数字も3でわれるという性質があります。

計算式
ごちゃごちゃしますが、下の計算でOKです。(上のほうが簡単ですね)
n = ((2 - (n - 1) % 3) * 10 + n) / 3;

まとめ

ずいぶん前にこういう感じのプログラムを書いたのを、昨日ふと思い出したので書きました。
ものすごく狭いノウハウなので実践で使うことはまずないだろうけれど、頭の体操としての練習課題としては楽しい問題です。
計算式が、こんなんでいいの?っていうくらい簡潔なのがいい。

2011/03/25

[C#]角度によるソート

このエントリーをはてなブックマークに追加
ランダムに打たれた点の集合Gのような入力あって、点Pが与えられたとき。
集合Gを、点Pを中心とした角度で並び替えたい。


何に使うかどうかはさておき、ちょっと何かの拍子に考えてしまったのでここに書いておきます。

・角度の求め方


まず角度の算出方法。
こちらを参照。

直線(ベクトル)の角度を求める
http://www.sm.rim.or.jp/~shishido/sheeta.html


単発で角度を求める分にはこれで問題ないようです。

角度を求めてそれを手がかりにソートさせれば、きっと想定する結果が求められそうはあります。
しかし、角度計算に使用する、アークサインも平方根の式もかなりの負荷となることは自明なので、少し考える。
そしてすぐに、単に外積を使えばできるのではと思い始める。

・C#における配列のソートの仕方


ソートに使用される比較方法を独自に指定するには、ListのSortメソッドに、IComparableを指定してあげます。
詳しくはこちら。

配列やコレクション内の要素を並び替える
http://dobon.net/vb/dotnet/programing/icomparer.html

取り出した二つの点を比較して、どちらが進行方向に向かってるのかの順序が判定できればいいだけです。
集合Gの任意の2点と点Pとのベクトルの外積の値が、正を持つのか負を持つのかで、どちら回りになっているのかが判定できるわけです。

ということで、実装してみる。

●角度でソート


//
private PointF p = PointF.Empty; // ←点P
private List<PointF> list = null; // ←集合G

private Random r = null; // 乱数作成用


private void Form1_Load(object sender, EventArgs e) {
r = new Random();

// 点Pと集合Gを作成する
p = GetRandomPoint();
list = CreatePointList();

list.Sort(Compare); // ソート

// これを描画するコードは省いています。
}

// 集合Gを作る
private List<PointF> CreatePointList(){
List<PointF> l = new List<PointF>();
for (int i = 0; i < 40; i++) {
l.Add(GetRandomPoint());
}
return l;
}

// ランダムに点をつくる
private PointF GetRandomPoint() {
int w = this.ClientRectangle.Width;
int h = this.ClientRectangle.Height;
return new PointF(r.Next(w), r.Next(h));
}

// 角度比較のためのコードはこれだけ
private int Compare(PointF a, PointF b) {
return (int)((a.X - p.X) * (b.Y - p.Y) - (b.X - p.X) * (a.Y - p.Y));
}


なお描画部分のコードは省いています。
ソート部分、仰々しいコードが必要かと思ったけれど、わりと簡潔に書けたのはよいこと。演算だけでよいなんて魔法のようです。
実行結果。下に仕上がりをざっと並べました。





before(ソート前)after(ソート後)

思いのほか爆発の吹き出しのような感じにならなかった。けれど、それは当然。普通に均等に点を配置しても、そもそも爆発の吹き出しの感じにはならないからでした。(三つ目の図形は人為的なものです)

できたけれど、はっきりいってこの比較方法では前も後ろもないわけで、ループするんじゃないの?と思っていたわけです。
でもループせずにうまくいきました。なんなんだろう。このあたりは留意事項。

●ランダムにソート


ちなみに、配列をぐちゃぐちゃにするほうは、戻り値を乱数にしてしまえばよろしいです。
簡単に書ける分、高速なやりかたではありませんので悪しからず。
// private Random r = new Random();
private int RandomCompare(PointF a, PointF b) {
return r.Next(); // 乱数を返す
}

2009/08/24

javascriptの数値計算を高速化

このエントリーをはてなブックマークに追加
javascriptだけじゃなくて、他の言語でも同じようなことです。
なお、javascriptの場合は、クライアントのスペック、および使用されるブラウザに依存する部分が強いようなので、必ずしも全てに当てはまらない可能性があるので注意してください。

なお、よく参照する関数は、var cos=Math.cosというようにあらかじめグローバル変数に代入しておくほうが、参照が減る分高速です。

●商
x/2よりx>>1のほうが高速。ただし、xは整数。同じように、乗算も、x*2よりもx<<1のほうが高速です。>Firefox3
x / 2 = 71
x * 0.5 = 70
x >> 1 = 64

->IE8
x / 2 = 156
x * 0.5 = 141
x >> 1 = 78

->GoogleChrome2
x / 2 = 6
x * 0.5 = 19
x >> 1 = 3

●べき乗
単純に平方を求めるなら、Math.powを使うより同じ数を二回掛けたほうが高速。
Math.pow(x,2)よりx*x。

1000000回テスト(単位はms)
※powはループ前に、var pow=Math.powとして計算したものです。

->Firefox3
Math.pow(x,2) = 321
pow(x,2) = 215
x * x = 95

->IE8
Math.pow(x , 2) = 531
pow(x , 2) = 328
x * x = 94

->GoogleChrome2
Math.pow(x,2) = 143
pow(x,2) = 136
x * x = 4

●小数点以下の切り捨て(いわゆる整数化)
Math.floor(x)よりも、x|0のほうが早い。

1000000回テスト(単位はms)
※ floorはループ前に、var floor =Math. floorとして計算したものです。

->Firefox3.0
Math.floor(x) = 249
floor(x) = 108
x | 0 = 128

->IE8
Math.floor(x) = 468
floor(x) = 219
x | 0 = 94

-> GoogleChrome2
Math.floor(x) = 64
floor(x) = 67
x | 0 = 17

●距離判定
点p1(x1,y1)と点p2(x2,y2)の距離dが、範囲rよりも小さいかどうかの判定だけなら、Math.sqrtを使わなくてもできる。

d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); // 距離の平方
r2 = r * r; // 範囲の平方
if(d2 < r2){
// 近い
}