プログラミングの質問です。

http://www.nposhifa.net/wp-content/uploads/2015/08/0f0d47b8a2a400cb0074e360e215a0bd.pdf
のようなトーナメント表をデータから表示するプログラムが可能なのかどうかおしえてください。
表の特徴は、シードチームが2回戦から登場、3回戦から登場、などがあります。
チーム数から自動的にトーナメント表を出力するプログラムは見たことがありますが、上記の条件では使用不可能と思われます。
回答方法は、URL、またはサンプルプログラムそのものでお願い致します。

回答の条件
  • 1人1回まで
  • 13歳以上
  • 登録:2015/08/14 01:21:17
  • 終了:2015/08/21 01:25:03

回答(2件)

id:tukihatu No.1

牛乳先生(tukihatu)回答回数180ベストアンサー獲得回数322015/08/17 12:17:26

ポイント150pt

できないことはないですが…期待するようなプログラムにはならないと思います。(例はPHPっぽく)

$seed1 = 4;//第一シード高の数
$seed2 = 2;//第二シード高の数
$name = array("1高","2高","3高","4高" …);//高の名前


$seed1name = array("シード1高","強い高","特別高","常勝高");//第一シード高の名前
$seed2name = array("シード2高","主催高");//第二シード高の数

シード高が決まった高校ではない完全ランダムの場合は上、決まっている場合はしたのような処理も必要になります。
あとは、シードの数を考えてトーナメントを表示するプログラムを書けばいいとおもいます。

$team = 18;//全校数
$setfirst = ($set - $seed1 - $seed2)/$seed1 ;//シードを除いた数 割る シード1(小数切捨てしてその分はどこかに付け加える処理をつけないといけない)

$result = array();
for($i=0;$i<$seed1;$i++){//シード1は必ずバラけるのでその分会場を増やす
  $result[$i] = array();
  for($k=0;$k<$setfirst;$k++){//シードなし高の数だけまわす
    $rnd = rnd(0,$team-1);//ランダム数値発生
    $result[$i][] = array($name[$rnd],3);//高校名+シード順位
    delete $name[$rnd]//使った名前を配列から削除
  }
  //最後にシード高を追加
  $rnd = rnd(0,$seed1-1);//ランダム数値発生
  $result[$i][] = array($seed1name[$rnd],1);
  delete $name[$rnd]//使った名前を配列から削除
}

(このコードはdeleteが命令違うので動きませんがこんな感じ)
シード2が入ってませんが、シード1と同じように入れればいけると思います。
あとは作ったデータを使って、線を書いたりするコードを書けばOKかと。


ただ複雑なシードトーナメント表になるとプログラムの作りこみが大変だと思うので、自分で作ったほうが手っ取り早そうな気がします。

他1件のコメントを見る
id:tukihatu

組み合わせ決定後や試合結果を表にしたいのですね。
どうでしょうね…勝ち負けなどのデータをプログラムで表記していくのはさらに難しそうですね…
配列("優勝高","シード1","第一試合結果5-3","4-2","決勝戦3-0")
配列("1高","シード3","第一試合結果3-5")
とかデータ形式を決めて、配列に入っているデータによって表示を変える形になるのかな。

2015/08/19 10:36:11
id:khaie

校名 第1回戦 第2回戦 第3回戦 第4回戦
A校 * b c d
B校 a b c d
C校 * * * d
D校 a b c d
E校 * * c d
こんな感じの表はできたのですが…

2015/08/19 20:45:49
id:tobeoscontinue No.2

tobeoscontinue回答回数214ベストアンサー獲得回数542015/08/20 21:07:57

ポイント150pt

トーナメント表は二進木によく似ていますので二進木に表現する方法を考えます。
二進木なら括弧を使うのが一般的だと思いますが一般の人には解りにくいと思うので
回戦とチーム名の表から二進木を表現する方法を考えます。
言語の指定がなかったのでrubyを使いました。
表の表示はこれも指定がなかったのでHTML5のCANVASを使います。確認はDebianのiceweaselで行ないました。
IEではscriptの違いなどで表示できないかもしれません。
データとして

3,藤枝順心高校
1,清水FC女子
1,富士見FCガイア
1,静岡レディース
1,蒲原クラブ女子
2,桐陽高校 1,島田プリンセス 1,アスルクラロ沼津 3,磐田東高校
3,常葉橘高校 1,浜松泉FC 1,静岡大成高校 2,常葉橘中学校
1,東海大静岡翔洋高校 1,御殿場レディース 1,磐田北高校 1,ルクレMYFC 3,清水第八プレアデス

を用意します。
手順としてはデータを読み込み込んで二進木を生成します。この時に配置まで決めてしまいます。
本来なら二進木からCANVAS用の出力をすればいいのですが処理と表示のコードがごちゃまぜになるのでいったん文字列で出力しそれを今度はCANVAS用scriptに変換するという手順を取ります。

class Node
	@@org = Struct.new(:x, :y).new(0,0)
	def initialize(l, r=0, x=nil, y=nil)
		@label = l
		@round = r
		@childs = x ? [x, y] : []
		if x 
			lcp = x.cp
			rcp = y.cp
			@bounds = lcp+[ 160+48*r,lcp[1], 160+48*r,rcp[1], ]+rcp
		else
			@bounds = [ 0,@@org.y, 160,@@org.y, 160,@@org.y+64, 0,@@org.y+64, 0,@@org.y ]
			@@org.y = @@org.y+64
		end
#		print "#{@label}(#{x.label if x}, #{y.label if y}) #{@bounds.join(',')}\n"
	end
	attr_accessor :label, :round
  def cp() [@bounds[2], (@bounds[3]+@bounds[5])/2] end
	def find(label) # Enumerableのfindはブロックが値を返すとその値ではなく要素を返すので再帰では意味をなさない。
		@label == label ? self : @childs.inject(nil) {|f, c| f ? f : c.find(label) }
	end
	def flatten
		@childs.map {|c| c.flatten }.flatten+
		[([@label,@round]+@bounds).join(',')]
#		["#{@label},#{@round},#{@bounds.join(',')}"]
	end
end

クラスNodeが二進木の要素です。座標を覚えておく必要があるので多少ごちゃごちゃしています。16を基調とした定数を使っているので状況に合わせて変更する必要があります。
@boundsは表示に必要な座標値で都合があってパスで表しています。ので四角は5ポイントになります。
cp()はチーム名ボックスの右中央の座標です。
find()は二進木の中からlabelのNodeを探すものです。
flatten()は各Nodeの内容を文字にして出力し下記のようになります。

藤枝順心高校,3,0,0,160,0,160,64,0,64,0,0
清水FC女子,1,0,64,160,64,160,128,0,128,0,64
富士見FCガイア,1,0,128,160,128,160,192,0,192,0,128
M1,2,160,96,256,96,256,160,160,160
静岡レディース,1,0,192,160,192,160,256,0,256,0,192
蒲原クラブ女子,1,0,256,160,256,160,320,0,320,0,256
M2,2,160,224,256,224,256,288,160,288
M7,3,256,128,304,128,304,256,256,256
M11,4,160,32,352,32,352,192,304,192
桐陽高校,2,0,320,160,320,160,384,0,384,0,320
島田プリンセス,1,0,384,160,384,160,448,0,448,0,384
アスルクラロ沼津,1,0,448,160,448,160,512,0,512,0,448
M3,2,160,416,256,416,256,480,160,480
M8,3,160,352,304,352,304,448,256,448
磐田東高校,3,0,512,160,512,160,576,0,576,0,512
M12,4,304,400,352,400,352,544,160,544
M15,5,352,112,400,112,400,472,352,472

def parse(t)
	agenda = t.split("\n").select {|s| s != ''}.map {|line|
		r, l = line.chomp.split(',')
		Node.new(l, r.to_i)
	}
	gcnt = Math.log2(agenda.inject(0) {|r, n| r + 2**(n.round-1) })
	m = 0
	1.upto(gcnt) {|r|
		agenda.each_with_index {|n, i| 
			if n.round == r then # nのroundがrならば次も同じrでなければならない。
				y = agenda.delete_at(i+1)
				agenda[i] = Node.new("M#{m=m+1}", r+1, n, y)
			end
	}	}
	agenda[0]
end

parseはトーナメントのデータから二進木ほ生成する部分です。
まずチームごとにNodeを生成してagendaへ追加していきます。
Math.log2(agenda.inject(0) {|r, n| r + 2**(n.round-1) })
は何回戦必要か計算します。
次にagendaから1回戦ペアーを取り出し2回戦として登録していきます。
同じくagendaから2回戦ペアーを取り出し3回戦として登録していくことを必要な回戦分繰り返すことで二進木として全体が登録されます。
エラーチェックはしていないのでdataが変だと止まります。

def ptpath(path, rvs)
	path.each_slice(2).map {|p|
		rvs ? [ rvs[0]-p[0].to_i, p[1].to_i-rvs[1] ] : [ p[0].to_i, p[1].to_i ]
	}
end

def canvas_to_html(games, rvs=nil)
	cout = ['context.font = "16px sans-serif";']
  puts games.join("\n")
	puts "\n"
	games.each {|game|
		label, round, *path = game.split(',')
		path = ptpath(path, rvs)
		x, y = path.shift
		cout << "context.beginPath();context.moveTo(#{x},#{y});"
		path.each {|x, y| cout << "context.lineTo(#{x},#{y});" }
		cout << "context.stroke();"
		if path.size == 3
			x, y = path.shift
			x = rvs ? x-8-label.length*8 : x+8			
			y = (y+path[0][1])/2-4
		else
			if rvs
				x = x-160/2-label.length*16/2
				y = y+32
			else
				x = x+160/2-label.length*16/2
				y = y+32
			end
		end
		cout << "context.fillText(\"#{label}\",#{x},#{y});"
	}
	cout.join("\n")
end

ptpathは座標値データで文字で入っているのでそれを数値に直すものです。

canvas_to_htmlはgamesにNodeの情報が文字列の配列で格納されているのでそれを展開しながらcanvas用のscriptに変更します。
反転も考慮しているので煩雑になっています。

root = parse ARGF.readlines.join
canvas_script = canvas_to_html(root.find('M15').flatten)+
									canvas_to_html(root.find('M16').flatten, [900,576])

template = <<"html"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>トーナメント表</title>
<script type="text/javascript">
<!--
function draw(context) { #{canvas_script} }
function drawcanvas(name) {
  //描画コンテキストの取得
  var canvas = document.getElementById(name);
  if (canvas.getContext)
    draw(canvas.getContext('2d'));
}
//-->
</script>
</head>
<body onLoad="drawcanvas('canvas0')">
<h2>トーナメント表</h2>
<h3 style="text-align: center;">平成27年度 第37回 全日本女子サッカー選手権 静岡県決勝大会<br>組 合 せ</h3>
<canvas id="canvas0" width="1024" height="1280" style="background-color:pink;">
図形を表示するには、canvasタグをサポートしたブラウザが必要です。
</canvas>
</body>
</html>
html

open("index.html", "w") {|f| f.write(template) }

root = parse ARGF.readlines.join
メインの流れはARGF.readlines.joinでデータを取り込みそれをparseします。二進木はrootに格納されます。

canvas_script = canvas_to_html(root.find('M15').flatten)+
canvas_to_html(root.find('M16').flatten, [900,576])
canvas_to_html(root.find('M15').flatten)はM15からのトーナメント表のcanvas用scriptを出力します。
canvas_to_html(root.find('M16').flatten, [900,576])はM16からのトーナメント表を反転移動したcanvas用scriptを出力します。
canvas_script = canvas_to_html(root.flatten)とすることで全体のトーナメント表を表示させるとこも可能です。
ヒアドキュメントによってcanvas_scriptが埋め込まれたhtmlがtemplateに格納され
open("index.html", "w") {|f| f.write(template) }
でindex.htmlファイルに出力されます。

動作確認用ですのでコードは汚いです。結果も反映させたいのであれば入力データはカンマで区切られたCSVファイルのようなものなので情報の追加してNodeに反映させることで可能になるかと。
pdfにあるような綺麗なトーナメント表にするには条件の設定などが面倒になりあまり実用的ではないように感じました。

コメントはまだありません

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

絞り込み :
はてなココの「ともだち」を表示します。
回答リクエストを送信したユーザーはいません