noyのブログ

健康なエンジニアを目指す人のブログ

7つの言語 7つの世界 ~ Ruby の柔軟性を見よ

f:id:noy72:20180720205219j:plain

最近、『7つの言語 7つの世界』を読んでいます。Ruby, Scala のように広く知られている言語から、Io, Prolog などのあまり見たことがないような言語まで、計 7 つの言語の特徴を紹介するという本です。技術書ではなく読み物に近いです。単に読むだけでは面白くないので、少しだけですが実際に言語を触ってみました。

私自身は C++Java をほんのちょっとだけ触った程度です。

第2章は Ruby です(第1章は、はじめに)。

特徴

If it walks like a duck and quacks like a duck, it must be a duck

つまりどういうこと?

test関数でoutputを呼んでいる。このとき、test関数にどのようなインスタンスを渡しても、output関数さえ持つなら期待された通りに動く。

C++のテンプレートを使って書くと以下のようになる。

つまるところ、必要な機能を持っているのならば、それは期待されているオブジェクトとして見なすことができるということ(自信なし)。

長所

短所

  • パフォーマンス
    そもそもパフォーマンスを重視している言語でない。
  • 平行性とOOP
    これは Ruby に限った話じゃない気がする。
  • 型の安全性
    動的型付けだから仕方ない。

1日目

セルフスタディ

探してみよう

  • Ruby の範囲に関する情報 (p18)

範囲ってスコープのことかな? - 変数と定数 (Ruby 2.5.0)

試してみよう

文字列 "Hello, world" を出力する。

いつもの。

文字列 "Hello, Ruby" の中の "Ruby" という単語のインデックスを検索する。

Stringクラスにあるindexメソッドを使えば良い。

自分の名前を10回出力する

いくつか方法がある。forでもwhileでも当然書ける。
timesはFixnumのメソッドで、Fixnum回ループする。

ファイルに格納されている Ruby プログラムを実行する。

乱数で生成した数字と入力された数字を比較する

printは改行せずに出力する。出力を複数行う場合はコンマで区切る。
改行コードは "\n" で、 '\n' では改行しない。シングルクォーテーションは式を展開しないため、単なる文字列として表示してしまう。

2日目

配列

irb(main):001:0> a = []
=> []
irb(main):002:0> a[0] = 12345
=> 12345
irb(main):003:0> a[1] = "asdf"
=> "asdf"
irb(main):005:0> a[3] = [1,2,3]
=> [1, 2, 3]
irb(main):006:0> a
=> [12345, "asdf", nil, [1, 2, 3]]

配列に複数の型を次々と追加できる。怖い。

irb(main):001:0> a = [1,2,3]
=> [1, 2, 3]
irb(main):002:0> a.push('a')
=> [1, 2, 3, "a"]
irb(main):003:0> a.pop
=> "a"
irb(main):004:0> a
=> [1, 2, 3]
irb(main):005:0> a.pop
=> 3
irb(main):006:0> a
=> [1, 2]

配列は順序付きコレクションなので幾つかのメソッドが使える。
参考:Array - Rubyリファレンス

irb(main):001:0> a = [1, 2, 3]
=> [1, 2, 3]
irb(main):002:0> a[0]
=> 1
irb(main):003:0> a[-1]
=> 3

セグフォではない。 シンタックスシュガーのおかげ。

ハッシュ

irb(main):001:0> num = {1 => "one", 2 => "two"}
=> {1=>"one", 2=>"two"}
irb(main):002:0> num[1]
=> "one"
irb(main):003:0> num[2]
=> "two"
irb(main):004:0> num = {:array => [1, 2, 3]}
=> {:array=>[1, 2, 3]}
irb(main):005:0> num[:array]
=> [1, 2, 3]

C++で言う所の map。Value と Key はなんでも良い。

コードブロック

コードブロックとは名前のない関数である。do ~ end 、あるいは {} で囲うことでコードブロックになる。

前述した times を利用したコード、

これは、"noy"という文字列を表示するというコードブロックを 10 回実行している。

出力
name
noy

main -> pass_block -> call_back と、二つのputsを持つコードブロックが渡されている。

ラムダ式みたいな感じ?

Mixin

モジュールという関数と定数の集まりをクラス混ぜ込むことで、クラスは混ぜ込まれた振る舞いと定数を持つ。

この例では、output モジュールは get の返り値を出力する。ここで、get がいったいなんなのかは書かれていない。Java であればインタフェースが必要になるが Ruby はダックタイピングがあるためインタフェースがいらない。

Output モジュールが混ぜ込まれた Number クラスは、値を出力する out メソッドを持っている。モジュールを混ぜ込むことで機能を追加することができる。

Javaで書くとこうかな?

あれ、インタフェース……

セルフスタディ

探してみよう

配列からハッシュへ、ハッシュから配列へ

参考:ハッシュに含まれるキーや値を配列として取得 - ハッシュ - Ruby入門
参考:配列からハッシュを作成する - ハッシュ(Hash)クラス - Ruby入門

ハッシュの各要素を繰り返す

参考:ハッシュに対する繰り返し - ハッシュ - Ruby入門 

配列で実現可能なデータ構造

計算量を考えないのであれば、
- スタック (pop, push)
- キュー (drop, push)
- デック (unshift, push, drop, pop)

insert や delete も使えるから、割となんでも実現できそう。

やってみよう

ファイル内で、ある文字列を含む行を表示する簡易 grep を作る

ruby grep.rb data.txt 0719

ARGVにコマンドラインの引数が入る。一つ目でファイルを指定、二つ目で探す文字列を入力する。あとは1行ずつ読み出して、indexメソッドで判定する。探す文字列が含まれていない場合は nil が返る。

3日目

オープンクラス

Numeric クラスは数値を表すクラスで、Integer や Float クラスのスーパークラスである。よって、Numeric クラスに tax メソッドを追加すると、Numeric クラスのサブクラスは tax メソッドを使うことができる。

同じ名前のクラスが二つ以上ある場合でも実行できる。上記の例では、Text クラスは initialize メソッド、 tax メソッド、 sale メソッドを持つ。

このように、クラスが一度定義されると、それ以降の同じ名前のクラスは 変更(メソッドの追加など) を行う。

クラス名、メソッド名、メソッドのシグニチャが全てば同じであれば、単に上書きする。

method_missing

method_missingは、存在しないメソッドが呼ばれた時の動作を定義する。上記の例では、メソッド名に含まれる文字をソートした配列を返す。

存在しないメソッドを読んでもエラーを返さなくなる(代わりに method_missing メソッドが呼ばれる)ので、デバッグが難しくなる恐れがある。

参考:method_missing - Rubyリファレンス

セルフスタティ3日目

試してみよう

上記のプログラムを変更し、

というAPIを使えるようにせよ。

とりあえず、CSVファイルを読み取るようにする。each メソッドで table.each を使いファイルの中身を舐める。CSV::Row クラスで method_missing を定義し、CSV::Row.[key] と書かれたら、シンボル key をキーにする(わかりにくい)バリューを返す。

多分、書籍が想定した問題と違うことをしている。

所感

C++Javaと比べて柔軟性がかなり高いと思った。割となんでもできる。動的型付けの言語はほとんど触ったことがなかったので、型がバラバラすぎない……? 考えなさすぎじゃない……? とやってはいけないことをしている気分になった。あと、計算量どうなるのという不安も。

静的型付け -> 動的型付け 「型がなくて不安だけど楽」
動的型付け -> 静的型付け 「柔軟性なさすぎ。。もうマジ無理。。」
ってなりそう。

コミュニティ大きいし、web系に使えるし、使いやすいと思うので、とても良いと思いました(小並感)。

(クソコード) 変数宣言と同時に標準入力を受け取りたい [C++編]

得られる情報はないです。

二つに分かれる

C++ で標準入力をするなら、どのようなコードを書きますか?
cinを使うのであれば、

int a;
cin >> a;

このように書けば良いですね。基礎中の基礎です。

時々、

int a; cin >> a;

のように、改行しない一行のコードを見ることがあります。気持ちはわかります。

どうにかして、変数の宣言/入力をまとめてできないでしょうか。

in()でもできない

cinしてその値を返すだけの関数を作ってみるとどうでしょうか。

int in(){
  int x;
  cin >> x;
  return x;
}

これを使えば変数宣言から入力までをまとめて行えます。
in関数で普通にcinしてますが

int a = in();

問題点は 型がintでなければ使えない ことです。

template<typename T> T in(){
  T x;
  cin >> x;
  return x;
}

テンプレートを使うことで型の問題は解消されますが、残念なことにタイプ数が増えます。

int a = in<int>();
double b = in<double>();

各型についてin関数を作るという方法もありますが、それはなんかいやなのでしません。 どうにかして型名を書くのを避けられないでしょうか?

そしてクソコードへ

class Void{ public: Void() { } }
const Void IN;

class Int{
private:
  int value;
public:
  Int() { }
  Int(Void IN){
    int x;
    cin >> x;
    value = x;
  }
}

int main(){
  Int a(IN);
}

いろいろなものを犠牲にすることで宣言と入力をまとめて行えるようになりました。 入力部分のみに着目すると、タイプ数を3文字(全体の23%)削減することができます。

各型ごとにクラスが必要になるから手間が増えただけ

このままだとvalueにアクセスする手段がないので(publicにしたりget()を作ってもいいですが)、以下の変換関数をクラスに加えておきます。

operator int() const { return value; }

すると、あたかもintであるかのように扱えるようになります。
参考:https://msdn.microsoft.com/ja-jp/library/wwywka61.aspx

  Int a(IN);
  cout << a << endl; // OK
  cout << a + 10 << endl; // OK

それができたからといってどうにもならないですが。

クイズ

突然ですがクイズです。以下の文はどのように動くでしょうか。

vector<Int> a(5, IN);

正解は、「一度だけ入力を受け取り、その値を使って全ての要素を初期化する」でした。

vectorもまとめたい

vectorの宣言と同時に複数の入力を受け取る、ということも実現したかったのですがその方法が思いつきませんでした。

in関数を使えばできます。ただし、型名を書く必要はありますが。

以下の入力を受け取るとします。全てintに収まる整数です。

N
A_1 A_2 ... A_N
template<typename T> T in(){
  T x;
  cin >> x;
  return x;
}
template<typename T> vector<T> in(int n){
  vector<T> x(n);
  for(int i = 0; i < n; i++) x[i] = in();
  return x;
}

int main(){
  auto a(in<int>(in<int>()))
}

かなり奇妙に感じるコードになりました。 in関数が奇妙に感じるのは当然としても、autoがコンストラクタでも型推論をしてくれるのは意外でした。

vector<int> a = {1, 2, 3};
auto b = a; // <- わかる
auto c(a); // <- !?

冷静になって考えてみると、奇妙の原因は何をするのかわからないin関数が入れ子になっていることのような気がします。

議論

今のままではできることが減ったintでしかありません。メンバ関数を増やして機能を補いましょう。
結局各型ごとに、例えば Int, Double, String クラス等を作る羽目になりますが、BaseTypeみたいなクラスを作って継承すれば手間は少なくなります。

本当はある部分に変数名を書くだけで入力までしてくれる、というものを考えていたんですが、思いつかないのでやめました。

あとC++編とありますが別の編はないです。
もしかすると言語によってそういうのできるのかなぁ……

結論

普通に書いて。

AOJ 0110 Alphametic

覆面算 | Aizu Online Judge

概要

A + B = C
という形式で式が与えられる。式の長さは 126 以下である。
与えられる数値にはひとつ以上の X が含まれている。

X0 以上 9 以下の整数である。
X に当てはまる整数を求めよ。当てはまる整数がない場合は NA を出力せよ。

実装

X に当てはまる数値を全探索する。
多倍長整数でなければオーバーフローする。

C++には多倍長整数はない。

ではどうするか。

modを使うのである。

この嘘解法を落とすケースはあるようで、 mod 10^9 + 7 と mod 10^9 + 9 ではWA。
よくわからない大きな素数でmodを取るとACする

#include<bits/stdc++.h>
#define range(i,a,b) for(int i = (a); i < (b); i++)
#define rep(i,b) for(int i = 0; i < (b); i++)
#define all(a) (a).begin(), (a).end()
#define show(x)  cerr << #x << " = " << (x) << endl;
//const int INF = 1e8;
using namespace std;
const long long M = 67280421310721LL;

vector<string> split(string in, char sp = ' '){
	vector<string> ret;
	stringstream ss(in);
	string s;
	while(getline(ss, s, sp)){
		ret.emplace_back(s);
	}
	return ret;
}

void modmod(vector<string> sp, int start){
	range(i,start,10){
		vector<long long> a(3,0);
		rep(j,3){
			for(auto c : sp[j]){
				(a[j] *= 10) %= M;
				if(c == 'X') a[j] += i;
				else a[j] += c - '0';
			}
		}
		if((a[0] + a[1]) % M == a[2] % M){
			cout << i << endl;
			return;
		}
	}
	cout << "NA" << endl;
	return;
}

int main(){
	string s;
	while(cin >> s){
		rep(i,s.size()) if(s[i] == '+') s[i] = '=';
		vector<string> sp = split(s, '=');

		int start = 0;
		for(auto& i : sp) if(i.front() == 'X' and i.size() > 1) start = 1;
		modmod(sp, start);
	}
}

Firebaseをandroidアプリに追加したときにつまずいたポイント

androidアプリにFirebaseを追加する

これは、Firebaseコンソールから行う。

名前や鍵を設定し、google-services.jsonをダウンロードし、所定の位置に置く。
その後、gradleファイルを変更する。

app_name/app/src/build.gradleを以下のように変更。

dependencies {
  ...
  compile 'com.google.firebase:firebase-core:11.8.0'
  ..
}
apply plugin: 'com.google.gms.google-services'

app_name/build.gradleを以下のように変更。

buildscript {
  ...
  dependencies {
    ...
    classpath 'com.google.gms:google-services:3.2.0'
  }
}

次に、画面上側のオレンジのバーのSyncをクリックする。
しかし、エラー。

Failed to resolve: com.google.firebase:firebase-core:9.0.0

解決策

app_name/build.gradleを以下のように変更。

allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    }
}

File google-services.json is missing. The Google Services Plugin cannot function

解決策

google-services.jsonは、app_name直下ではなく、app_name/appに置く。

しかし、エラー。

No matching client found for package name '〇〇'

解決策

app_name/app/google-services.jsonの以下の部分を変更。

"client": [
{
  "client_info": {
    "mobilesdk_app_id": "9:99999999:android:9ccdbb6c1ae659b8",
    "android_client_info": {
      "package_name": " ここを一致させる "
    }
  }
}

com.exampleになっていたので、正しいものに変更する。

コップ本でScala入門 - 2 配列、リストなど

配列

関数型なのに配列を!?

string a[3];
a[0] = "ab";
a[1] = "cd";
a[2] = "ef\n";
for(int i = 0; i < 3; i++){
    cout << a[i];
}
val a = new Array[String](3)
a(0) = "ab"
a(1) = "cd"
a(2) = "ef\n"
for(i <- 0 to 2){
    print(a(i))
}

生成するインスタンスの構成を設定する事をパラメーター化という。
上記の例では、Stringという型パラメーター、3という値パラメーターを設定している。

for式にある to はメソッドである。
詳しく書くと、こうなる。

for(i <- (0).to(2))

数値はIntオブジェクトである。
toというメソッドを呼び出し、Intオブジェクトである2を渡している。
もっというと、すべての演算がメソッド呼び出しである。

val num = Array(1,2,3)

このような初期化もできる。

リスト

配列は変更可能(ミュータブル)であるが、
Scalaのリストは変更不能(イミュータブル)である。
※可変リストもある

val a = List(1,2)
val b = 0 :: a
val c = b ::: a
println(b)
println(c)
出力
List(0, 1, 2)
List(0, 1, 2, 1, 2)

セミコロンで整数をくっつけたり、リストをくっつけたりできる。
Listメソッドにはいろいろある。

その中でも関数言語感を感じるmapの紹介を。

val list = List(1,2,3,4)
println(list.map(a => a * a))
出力
List(1, 4, 9, 16)

リストの各要素に関数を適用する。

タプル

イミュータブルなコンテナオブジェクト。
異なる型の要素を持つ事ができる。

val pair = (1, "a")
val tuple = (1, "a", 3.1)
println(pair._1)
println(tuple._3)
println(tuple)
出力
1
3.1
(1,a,3.1)

ここで、tupleの型はTuple3[Int, String, Double]になっている。

0-indexではないが、これは静的に型付けされたタプルは1から始まるという伝統のためである。

scala> val t = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)                                             
t: (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)

要素をたくさん増やしたくなるが、23個以上はエラーが返ってくる。

集合とマップ

Scalaにはミュータブルなコレクションとイミュータブルのコレクションが区別されている。

イミュータブルなset

var set = Set(1,2,3)
set += 4
println(set.contains(4))
出力
true

setに要素を追加するには+演算子を使う。

イミュータブルなmap

val map = Map(1 -> "a", 2 -> "b", 3 -> "c")
println(map(2))
出力
b

イミュータブルなmapに要素を追加しようとするとエラーが返る。
追加したい場合は、ミュータブルなmapを使う。

test.scala:3: error: value += is not a member of scala.collection.immutable.Map[Int,String]               
Expression does not convert to assignment because receiver is not assignable.                                       
map += (4 -> "a")

Scalaプログラマーに求められる態度

val、イミュータブルオブジェクト、副作用のないメソッドを優先する。
理由があるなら、var、ミュータブルオブジェクト、副作用のあるメソッドを使う。

Haskellは後者を許さない(はず)。一度Haskellに挑戦した事があるが、簡単なコードを書くのにもかなり苦労した。
その点Scalaは融通が利いて良いが、常にJavaとして書いてしまうと関数型言語の考えが身につかないので、うまく使い分ける必要がありそう。

まとめ

  • 配列が使える
  • コレクションはイミュータブルとミュータブルが区別される
  • val、var、イミュータブル、ミュータブルは使い分ける


コップ本でScala入門 - 1 Scalaとは / if、for - noyのブログ
次 ないです

コップ本でScala入門 - 1 Scalaとは / if、for

はじめに

C言語でプログラミングのセンスがあるかどうかはわからないけど、
関数型言語わからないのはセンスがないよねw」

き 僕 教 な
ず の 授 に
つ 心 の げ
け を 冗 な
談 い
◯ ◯

目標

Scalaスケーラブルプログラミング第3版の内容を理解する。
そしてWebアプリケーション開発へ。
多分失踪する。

Scala とは

関数型プログラミング言語である
でもJavaっぽく書くこともできる。
純粋なオブジェクト指向言語関数型言語が両方そなわり最強な静的型付け言語に見える。初心者が使うと逆に頭がおかしくなって死ぬ。

スクリプト言語のように見えたりもする。

ロゴが螺旋階段

言語別給与ランキングでは上位にいる
需要は少ないが、それ以上に供給が少ない(と思っている)。

scalaを選ぶ理由

互換性
簡潔性
高水準
静的な型付け

プログラミング第一歩

変数

val : 代入不可
var : 代入可能

C++で言うところのこれが、

int a = 5;
const int b = 10;

こう。

var a = 5
val b = 10

型推論の機能を持つので、型名を省略することができる。
セミコロンも省略可。

関数

int max(int a, int b){
	if(a > b) return a;
	else return b;
}

max関数の実装。Scalaでは以下のように実装する。

def max(a: Int, b: Int): Int = {
	if(x > y) x
	else y
}

def 関数名 (変数名: 型, ...): 結果型 = {
	関数本体
}

Scalaでは戻り型を結果型という。
また、三項演算子のようにif式が値を返す。

def greet() = println("Hello, world!")

関数のカーリーブラケットは省略可能。
C++ならこのような関数の戻り型はvoidだが、ScalaではUnitという結果型になる。

インクリメント

i++のような書き方はできない。

forループ

argsの要素を一つずつ表示するコードをC++で書くとこうなる。

for(auto arg : args){
	cout << arg << endl;
}

Scalaはこうなる。

for(arg <- args){
	println(arg)
}

数学記号で言う所の(要素 ∈ 集合)が、Scalaの(要素 <- 集合)に当たる。

また、このような書き方もできる。

for_each(args.begin(), args.end(), [](string arg){ cout << arg << endl;});
args.foreach(arg => println(arg))
args.foreach((arg: String) => println(arg))

(変数名: 型, ...) => 関数本体 //関数リテラル


さらに、一個の引数をとる一文からなる関数リテラルの場合、色々略せる。

args.foreach(println)

短い。

実践

A - Product

object Main extends App {
  val in = scala.io.StdIn.readLine.split(" ").map(_.toInt)
  def func(x: Int): String = if (x % 2 == 1) "Odd" else "Even"
  println(func(in(0) * in(1)))
}

ちなみに、

if (x % 2) "Odd" else "Even"  

だとエラーが返ってくる。

まとめ


競技プログラマーが始めるScala - 2 配列、リストなど - noyのブログ

技術室奥プログラミングコンテスト E - 不可視境界線 (The Invisible Borderline)

E - 不可視境界線 (The Invisible Borderline)

問題概要

辺にコストがある木が与えられる。
各頂点から最も遠い点を求めよ。

考察

木の直径を用いて解を求めることを考えます。

f:id:noy72:20180111095259p:plain:w500

まず、最長パス p_0, p_1, p_2, ... , p_k を求めます。
赤い部分は最長パスに含まれることを表しています。

すると、最長パスに含まれる頂点と、最長パスに含まれない部分木に分けることができます。

最長パスに含まれる頂点 p_i の最遠点

p_0 から p_i までの距離と、 p_i から p_k の距離を比較すれば良いです。

部分木に含まれる頂点の最遠点

ある部分木 s_{p_i} に含まれる全ての頂点の最遠点は、 p_i の最遠点と一致します。
一致しないと仮定すると、最長パスであることと矛盾します。

  • 最長パスの一部を含まないようなパスよりも、含むパスの方が必ず長くなる。
  • 最長パスの一部を経由して別の部分木に入るようなパスよりも、入らないパスの方が必ず長くなる。


最遠点が複数ある場合は、頂点番号が小さいものを出力しなければいけないので、そこだけ注意します。

コード

※最長パスが複数ある場合を考えていないのにACしたコード

  1. bfsで最長パスを求める
  2. 最長パスに含まれている頂点の最遠点を求める
  3. 部分木を求める
  4. 部分技に含まれる頂点の解を求める
  5. 出力
#include<bits/stdc++.h>
#define range(i,a,b) for(int i = (a); i < (b); i++)
#define rep(i,b) for(int i = 0; i < (b); i++)
#define all(a) (a).begin(), (a).end()
#define show(x)  cerr << #x << " = " << (x) << endl;
using namespace std;

#define int long long
const int INF = (1LL << 60);

const int MAX_V = 100005;

class Edge{
	public:
		int to, dis;
		Edge(){}
		Edge(int to, int dis): to(to), dis(dis)  {}
};

vector<Edge> g[MAX_V];
int dis[MAX_V];

vector<int> bfs(int s, int n, bool f){
	queue<int> q;

	rep(i,n) dis[i] = INF;
	dis[s] = 0;
	q.push(s);

	vector<int> pre(n,-1);
	while(not q.empty()){
		int d = q.front(); q.pop();

		rep(i,g[d].size()){
			Edge e = g[d][i];
			if(dis[e.to] == INF){
				pre[e.to] = d;
				dis[e.to] = dis[d] + e.dis;
				q.push(e.to);
			}
		}
	}

	int maxi = -1;
	rep(i,n){
		if(dis[i] == INF) continue;
		if(maxi == -1) maxi = i;
		if(dis[maxi] < dis[i]){
			maxi = i;
		}
	}
	if(f) return vector<int>() = {maxi};

	vector<int> path = {maxi};
	while(pre[maxi] != -1){
		maxi = pre[maxi];
		path.emplace_back(maxi);
	}
	reverse(all(path));
	return path;
}

//---------------------------- union-find
int par[MAX_V]; //親
int depth[MAX_V];//木の深さ

void init(int n){
	rep(i,n){
		par[i] = i;
		depth[i] = 0;
	}
}

int find(int x){
	if(par[x] == x){
		return x;
	}else {
		return par[x] = find(par[x]);
	}
}

void unite(int x, int y){
	x = find(x);
	y = find(y);
	if(x == y) return;

	if(depth[x] < depth[y]){
		par[x] = y;
	}else{
		par[y] = x;
		if(depth[x] == depth[y]) depth[x]++;
	}
}

bool same(int x, int y){
	return find(x) == find(y);
}
//----------------------------

signed main(){
	int n;
	cin >> n;

	vector<pair<int, int>> e(n - 1);
	rep(i,n - 1){
		int a, b, c;
		cin >> a >> b >> c;
		a--; b--;
		e[i] = make_pair(a,b);
		g[a].emplace_back(b,c);
		g[b].emplace_back(a,c);
	}

	vector<int> path = bfs(bfs(0, n, 1).front(), n, 0); //最長パスを求める
	vector<bool> used(n,0);
	vector<int> ans(n);
	for(auto i : path){
		used[i] = true;
		if(dis[i] > dis[path.back()] - dis[i]){
			ans[i] = path.front();
		}else if(dis[i] < dis[path.back()] - dis[i]){
			ans[i] = path.back();
		}else{
			ans[i] = min(path.front(), path.back());
		}
	}

	init(n);
	rep(i,n - 1){ //部分木を求める
		if(used[e[i].first] and used[e[i].second]) continue;
		unite(e[i].first, e[i].second);
	}

	vector<int> par(n);
	for(auto i : path){
		par[find(i)] = ans[i];
	}
	rep(i,n){
		if(used[i]) continue;
		ans[i] = par[find(i)];
	}

	for(auto& i : ans){
		cout << i + 1 << endl;
	}
}