C# で Key Vault にシークレットを登録する

Azure.Security.KeyVault.Secrets を使えば、Microsoft Azure Key Vault にシークレットを登録できた。

www.nuget.org

以下サンプルコード。シークレットの取得もやってる。

using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

var client = new SecretClient(
    vaultUri: new Uri("https://your-vault-uri/"),
    credential: new DefaultAzureCredential());

var name = "your-key-name";
await client.SetSecretAsync(name: name, value: "your-secret-value");

var response = await client.GetSecretAsync(name);

ExistsAsync とか欲しい。

C# で Microsoft Entra ID にアプリを登録する方法

Microsoft Graph SDK を使う。Azure SDK ではなかった。

www.nuget.org

アプリが既に存在するか検索し、なければ登録するコードは次の通り。

using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Graph.Applications.Item.AddPassword;
using Microsoft.Graph.Models;

var graphServiceClient = new GraphServiceClient(
    new DefaultAzureCredential());

const string applicationDisplayName = "my-entra-id-application";
var applications = await graphServiceClient.Applications.GetAsync(x =>
{
    x.QueryParameters.Filter = $"displayName eq '{applicationDisplayName}'";
});
var application = applications?.Value?.FirstOrDefault();
if (application == null)
{
    // アプリを登録
    var newApplication = new Application
    {
        DisplayName = applicationDisplayName,
        SignInAudience = "AzureADMyOrg",
    };
    application = await graphServiceClient.Applications
        .PostAsync(newApplication);
}
Console.WriteLine($"AppID: {application?.Id}");
Console.WriteLine($"AppDisplayName: {application?.DisplayName}");

var principals = await graphServiceClient.ServicePrincipals.GetAsync(x =>
{
    x.QueryParameters.Filter = $"appId eq '{application!.AppId}'";
});
var principal = principals?.Value?.FirstOrDefault();
if (principal == null)
{
    // サービスプリンシパルを作成
    var newPrincipal = new ServicePrincipal
    {
        DisplayName = applicationDisplayName,
        AppId = application!.AppId,
        AccountEnabled = true,
    };
    principal = await graphServiceClient.ServicePrincipals.PostAsync(newPrincipal);
}
Console.WriteLine($"ServicePrincipalId:{principal?.Id}");

const string credentialDisplayName = "my-app-secret";
var credential = application?.PasswordCredentials?.Find(x => x.DisplayName == credentialDisplayName);
if (credential == null)
{
    // シークレットを登録
    var newPasswordCredential = new PasswordCredential
    {
        DisplayName = credentialDisplayName,
        EndDateTime = DateTimeOffset.UtcNow.AddYears(1),
        KeyId = Guid.NewGuid(),
    };
    credential = await graphServiceClient.Applications[application!.Id]
        .AddPassword.PostAsync(new AddPasswordPostRequestBody()
        {
            PasswordCredential = newPasswordCredential,
        });

    // SecretText は readonly で、作成直後しか取得できない
    Console.WriteLine($"PasswordCredentialSecretText: {credential?.SecretText}");
}
Console.WriteLine($"PasswordCredentialID:{credential?.KeyId}");
Console.WriteLine($"PasswordCredentialDisplayName: {credential?.DisplayName}");

IAMでアプリにロールを割り当てたいので、サービスプリンシパルも作成しておく必要があった。

シークレットも無ければ登録している。登録した際のレスポンスでしかシークレットの値を取得できない。後で取得しようとしたらnull。後から取得できないのはポータルと同じだな。

B09 - Papers

atcoder.jp

ABとCDが座標というのを理解していなくて、1まわり大きい面積を計算してしまっていた。実際に手計算して気付けたけど、結構嵌まってしまったな。

var N = int.Parse(Console.ReadLine()!);
var A = new int[N];
var B = new int[N];
var C = new int[N];
var D = new int[N];
for (var i = 0; i < N; i++)
{
    var ABCD = Console.ReadLine()!.Split(' ').Select(x => int.Parse(x)).ToList();
    (A[i], B[i], C[i], D[i]) = (ABCD[0], ABCD[1], ABCD[2], ABCD[3]);
}
const int HEIGHT = 1500 + 1;
const int WIDTH = 1500 + 1;
var X = new int[HEIGHT, WIDTH];
for (var i = 0; i < N; i++)
{
    var (x1, y1, x2, y2) = (A[i], B[i], C[i], D[i]);
    X[y1, x1]++;
    X[y1, x2]--;
    X[y2, x1]--;
    X[y2, x2]++;
}

var Z = new int[HEIGHT, WIDTH];
for (var y = 0; y < HEIGHT; y++)
{
    for (var x = 0; x < WIDTH; x++)
    {
        Z[y, x] = X[y, x];
        if (0 < x)
        {
            Z[y, x] += Z[y, x - 1];
        }
    }
}
for (var x = 0; x < WIDTH; x++)
{
    for (var y = 0; y < HEIGHT; y++)
    {
        if (0 < y)
        {
            Z[y, x] = Z[y - 1, x] + Z[y, x];
        }
    }
}

var answer = 0;
for (var y = 0; y < HEIGHT; y++)
{
    for (var x = 0; x < WIDTH; x++)
    {
        if (0 < Z[y, x])
        {
            answer++;
        }
    }
}
Console.WriteLine(answer);

A09 - Winter in ALGO Kingdom

atcoder.jp

配列のインデックスが配列に格納されているような形なんで、ミスしてもなかなか気付かずハマった。配列の中身を出力するコードを一時的に足して、間違っていた箇所にようやく気付けた。

var HWN = Console.ReadLine()!.Split(' ').Select(x => int.Parse(x)).ToList();
var (H, W, N) = (HWN[0], HWN[1], HWN[2]);
var (A, B, C, D) = (new int[N], new int[N], new int[N], new int[N]);
for (var i = 0; i < N; i++)
{
    var ABCD = Console.ReadLine()!.Split(' ').Select(x => int.Parse(x)).ToList();
    (A[i], B[i], C[i], D[i]) = (ABCD[0], ABCD[1], ABCD[2], ABCD[3]);
}

// N 日目の積雪をマッピング
var X = new int[H + 2, W + 2];
for (var i = 0; i < N; i++)
{
    X[A[i], B[i]]++;
    X[A[i], D[i] + 1]--;
    X[C[i] + 1, B[i]]--;
    X[C[i] + 1, D[i] + 1]++;
}

// 2次元累積和を計算
var Z = new int[H + 2, W + 2];
for (var h = 1; h <= H; h++)
{
    for (var w = 1; w <= W; w++)
    {
        Z[h, w] = Z[h, w - 1] + X[h, w];
    }
}
for (var w = 1; w <= W; w++)
{
    for (var h = 1; h <= H; h++)
    {
        Z[h, w] = Z[h - 1, w] + Z[h, w];
    }
}

// 答えを出力
for (var h = 1; h <= H; h++)
{
    for (var w = 1; w <= W; w++)
    {
        if (w > 1)
        {
            Console.Write(" ");
        }
        Console.Write(Z[h, w]);
    }
    Console.WriteLine();
}

B08 - Counting Points

atcoder.jp

点の座標を二次元配列にプロットしてから、二次元累積和を計算。

x座標用のループ変数はx、y座標用のループ変数はyと名前付けしたら、混乱が緩和された気がする。

const int H = 1500 + 1;
const int W = 1500 + 1;
var N = int.Parse(Console.ReadLine()!);
var XY = new int[H, W];
for (var i = 0; i < N; i++)
{
    var input = Console.ReadLine()!.Split(' ').Select(x => int.Parse(x)).ToList();
    var X = input[0];
    var Y = input[1];
    XY[Y, X]++;
}

// 横方向の累積和を計算
var Z = new int[H, W];
for (var y = 1; y < H; y++)
{
    for (var x = 1; x < W; x++)
    {
        Z[y, x] = Z[y, x - 1] + XY[y, x];
    }
}

// 縦方向の累積和を計算
for (var x = 1; x < W; x++)
{
    for (var y = 1; y < H; y++)
    {
        Z[y, x] = Z[y - 1, x] + Z[y, x];
    }
}

// 問題を読み込む
var Q = int.Parse(Console.ReadLine()!);
var a = new int[Q];
var b = new int[Q];
var c = new int[Q];
var d = new int[Q];
for (var i = 0; i < Q; i++)
{
    var input = Console.ReadLine()!.Split(' ').Select(x => int.Parse(x)).ToList();
    a[i] = input[0];
    b[i] = input[1];
    c[i] = input[2];
    d[i] = input[3];
}

// 解答する
for (var i = 0; i < Q; i++)
{
    var answer = Z[d[i], c[i]]
        + Z[b[i] - 1, a[i] - 1]
        - Z[b[i] - 1, c[i]]
        - Z[d[i], a[i] - 1];
    Console.WriteLine(answer);
}

A08 - Two Dimensional Sum

atcoder.jp

回答する際の、配列のインデックス指定で混乱した。変数名にもうちょい工夫が必要だったな。

var input = Console.ReadLine()!
    .Split(' ')
    .Select(x => int.Parse(x))
    .ToList();
var H = input[0];
var W = input[1];
var X = new int[H, W];
for (var i = 0; i < H; i++)
{
    input = Console.ReadLine()!
        .Split(' ')
        .Select(x => int.Parse(x))
        .ToList();
    for (var j = 0; j < W; j++)
    {
        X[i, j] = input[j];
    }
}

// 横平方向の累積和を計算
var Z = new int[H + 1, W + 1];
for (var i = 1; i <= H; i++)
{
    for (var j = 1; j <= W; j++)
    {
        Z[i, j] = Z[i, j - 1] + X[i - 1, j - 1];
    }
}

// 縦方向の累積和を計算
for (var j = 1; j <= W; j++)
{
    for (var i = 1; i <= H; i++)
    {
        Z[i, j] = Z[i - 1, j] + Z[i, j];
    }
}

// 問題を読み込む
var Q = int.Parse(Console.ReadLine()!);
var A = new int[Q];
var B = new int[Q];
var C = new int[Q];
var D = new int[Q];
for (var i = 0; i < Q; i++)
{
    input = Console.ReadLine()!
        .Split(' ')
        .Select(x => int.Parse(x))
        .ToList();
    A[i] = input[0];
    B[i] = input[1];
    C[i] = input[2];
    D[i] = input[3];
}

// 答えを求める
for (var i = 0; i < Q; i++)
{
    var answer = Z[C[i], D[i]]
        + Z[A[i] - 1, B[i] - 1]
        - Z[A[i] - 1, D[i]]
        - Z[C[i], B[i] - 1];
    Console.WriteLine(answer);
}

B07 - Convenience Store 2

atcoder.jp

前の時間帯と比べて何人増減したかを計算して、次にその累積和を計算。

var T = int.Parse(Console.ReadLine()!);
var N = int.Parse(Console.ReadLine()!);

// 例えば0時から10時までの営業で、
// 5時から10時まで働く従業員がいた場合、
// B[10]が範囲外にならないよう、
// 配列の長さを1個多くしておく。
var B = new int[T + 1];
for (var i = 0; i < N; i++)
{
    var input = Console.ReadLine()!
        .Split(' ')
        .Select(x => int.Parse(x))
        .ToList();
    var L = input[0];
    var R = input[1];
    B[L]++;
    B[R]--;
}

var Answer = new int[T];
Answer[0] = B[0];
Console.WriteLine(Answer[0]);
for (var i = 1; i < T; i++)
{
    Answer[i] = Answer[i - 1] + B[i];
    Console.WriteLine(Answer[i]);
}