たきこみの丸太

暇だった。

表示検討

ホロライブ課金者ランキング を作ってみたわけだが。
チャンネル情報を全部アイコン画像に載せる方法は良い感じ。スマホ特化のHatena Blogテンプレートでも見やすい。
こんなアイコンのみのランキング作っても誰が理解できるんだろう?は、不安材料では有ったが、有識者に「このランキングに知ってるアカウント有る?」と聞いてみたところ、「7割しってる」「たつのことふぁんでっどと野うさぎはたぶん全員わかる」との回答を頂きましたので、分かる人には分かるランキンにはなっている模様。
じゃ、動画ランキングもチャンネルランキングも基本アイコンでやる方向で良いかもしれん。

処理系はSQLメインでやる予定だったが、同一人物判定にc#依存箇所を作ってしまったので、もういくら使っても同じだろうと思いc#を多用している。SQLならメンドイから避けたい処理もc#なら舞える。


以下は前回からの変更点のみ。

前回からの変更点

チャンネルテーブルにサムネイルURL情報を追加。

これは、YouTube.v3.Data叩けば簡単に取れる。

public DtoChannel GetChannelInfo(string channelId)
{
    List<string> requestTypeList = new List<string>();
    requestTypeList.Add("id");
    requestTypeList.Add("snippet");
    //requestTypeList.Add("topicDetails");

    var searchListRequest = _youtubeService.Channels.List(requestTypeList);
    searchListRequest.Id = channelId;
    var channnelList = searchListRequest.Execute();
    if(channnelList.Items.Count == 0)
    {
        return null;
    }
    else
    {
        var channel = channnelList.Items.First();
        DtoChannel c = new DtoChannel();
        c.Id = channelId;
        c.PublishedAt = DateTime.Parse(channel.Snippet.PublishedAt);
        c.Thumbnail0 = LiveCommentCompleted.GetNotNullString(channel.Snippet.Thumbnails?.Default__?.Url);
        c.Thumbnail1 = LiveCommentCompleted.GetNotNullString(channel.Snippet.Thumbnails?.Medium?.Url);
        c.Thumbnail2 = LiveCommentCompleted.GetNotNullString(channel.Snippet.Thumbnails?.High?.Url);
        return c;
    }
}

順位の取得を同一人物判定と同じタイミングでやるように変更。

同一金額を同一順位にしたかった。

public class DtoPaidUser
{
    #region 定数

    /// <summary>
    /// グルーピングKeyになるための最小文字数
    /// </summary>
    private const int KEY_NAME_MIN_COUNT = 3;

    /// <summary>
    /// グルーピングKeyになるための最小金額
    /// </summary>
    private const decimal KEY_AMOUNT_MIN = 10000;

    /// <summary>
    /// グルーピングValueになるための最小金額
    /// </summary>
    private const decimal VALUE_AMOUNT_MIN = 10000;

    /// <summary>
    /// この金額を超えるアイテムが無い限りグループとしない
    /// </summary>
    private const decimal GROUPING_AMOUNT_MIN = 45000;

    #endregion

    #region プロパティ

    public long Rank { get; set; }
    public decimal Amount { get; set; }
    public string Name { get; set; }
    public string NameEdited { get; set; } = string.Empty;
    public string Id { get; set; }
    public string Url0 { get; set; }
    public string Url1 { get; set; }

    /// <summary>
    /// サブアカウントがない場合は空、ある場合は自分も含めた要素リスト
    /// </summary>
    public List<DtoPaidUser> InnerUserList { get; set; } = new List<DtoPaidUser>();

    #endregion

    /// <summary>
    /// 同一人物判定用文字列を設定する
    /// </summary>
    public void SetNameEdited()
    {
        NameEdited = Name.Replace(" ", string.Empty);
    }

    public string GetSubAccountMarker()
    {
        if(InnerUserList.Count != 0)
        {
            return "<br /><span style=\"font-size: 80%\">+ SUB</span>";
        }
        else
        {
            return string.Empty;
        }
    }

    /// <summary>
    /// 同一人物判定
    /// </summary>
    /// <param name="dtoPaidUserList">同一人物判定されていないリスト</param>
    /// <remarks>同一人物が集約されたリスト</remarks>
    public static List<DtoPaidUser> GetPaidUserListWithInnerUserList(List<DtoPaidUser> dtoPaidUserList)
    {
        foreach (var item in dtoPaidUserList)
        {
            item.SetNameEdited();
        }

        //自分と同一だと思われるアイテムをValueに詰める
        var userList = new Dictionary<DtoPaidUser, List<DtoPaidUser>>();
        foreach (var user in dtoPaidUserList)
        {
            //結合Keyとなるユーザーの選別
            if (user.NameEdited.Length >= KEY_NAME_MIN_COUNT && user.Amount >= KEY_AMOUNT_MIN)
            {
                ///結合対象となるユーザーの選別
                ///(
                /// ・名前を含んでいる。
                /// かつ
                /// ・IDが自分では無い。
                /// かつ
                /// ・特定金額以上
                /// )または
                /// (
                /// ・IDが自分と同じ
                /// かつ
                ///     (・名前が違う
                ///     または
                ///     ・サムネイル0が違う
                ///     または
                ///     ・サムネイル1が違う
                ///      )
                /// )
                var q =
                    from innerUser in dtoPaidUserList
                    where
                        (
                            innerUser.NameEdited.Contains(user.NameEdited)
                            && !user.Id.Equals(innerUser.Id)
                            && innerUser.Amount >= VALUE_AMOUNT_MIN
                        )
                        || 
                        (
                            user.Id.Equals(innerUser.Id) 
                            && 
                                (
                                    !user.Name.Equals(innerUser.Name)
                                    || !user.Url0.Equals(innerUser.Url0)
                                    || !user.Url1.Equals(innerUser.Url1)
                                )
                        )
                    select innerUser;

                var preInnerList = q.ToList();
                if(preInnerList.Count != 0)
                {
                    decimal maxAmount = preInnerList.Max(x => x.Amount);

                    //グループ内に一定金額以上のアイテムが無ければ、同一とはみなさない。
                    if (maxAmount > GROUPING_AMOUNT_MIN || user.Amount > GROUPING_AMOUNT_MIN)
                    {
                        userList.Add(user, preInnerList);
                    }
                    else
                    {
                        userList.Add(user, new List<DtoPaidUser>());
                    }
                }else
                {
                    userList.Add(user, new List<DtoPaidUser>());
                }
            }
            else
            {
                userList.Add(user, new List<DtoPaidUser>());
            }
        }

        //結合対象数が多い順でソート
        var groupedUserDic = userList.OrderByDescending(pair => pair.Value.Count).ToList();

        //Valueに詰めた同一アイテムをKeyから削除
        int counter = 0;
        while (true)
        {
            if (groupedUserDic.Count <= counter)
            {
                break;
            }

            var pair = groupedUserDic.ElementAt(counter);
            for (int i = 0; i < pair.Value.Count; i++)
            {
                //内部要素になったアイテムをKeyから削除する
                groupedUserDic.Remove(
                    (from item in groupedUserDic
                     where pair.Value[i].Id.Equals(item.Key.Id)
                     select item).First());

            }

            counter++;
        }

        var groupedUserList = new List<DtoPaidUser>();

        //同一人物判定されたオブジェクトの集計
        foreach (var keyValue in groupedUserDic)
        {
            if(keyValue.Value.Count() != 0)
            {
                //集約要素リストの作成
                List<DtoPaidUser> innerList = new List<DtoPaidUser>();
                innerList.Add(keyValue.Key);
                innerList.AddRange(keyValue.Value);

                //集約
                DtoPaidUser dpu = new DtoPaidUser();
                dpu.Amount = innerList.Sum(x => x.Amount);
                dpu.Name = keyValue.Key.Name;
                dpu.Id = keyValue.Key.Id;
                dpu.Url0 = keyValue.Key.Url0;
                dpu.Url1 = keyValue.Key.Url1;
                dpu.InnerUserList = innerList;

                groupedUserList.Add(dpu);
            }
            else
            {
                groupedUserList.Add(keyValue.Key);
            }
        }

        return GetRankedUserList(groupedUserList);
    }

    /// <summary>
    /// 金額ごとにランキング順位を設定したリストを取得する
    /// </summary>
    /// <param name="userList"></param>
    /// <returns></returns>
    public static List<DtoPaidUser> GetRankedUserList(List<DtoPaidUser> userList)
    {
        var orderedList = userList.OrderByDescending(x => x.Amount);

        //順位の設定
        int rankCounter = 0;
        int sameItemCounter = 0;
        decimal rankAmountValue = -1;
        foreach (var item in orderedList)
        {
            //同一金額の場合同一の順位を設定すための条件
            if (rankAmountValue.CompareTo(item.Amount) != 0)
            {
                rankCounter += sameItemCounter + 1;
                sameItemCounter = 0;
                rankAmountValue = item.Amount;
            }
            else
            {
                sameItemCounter++;
            }

            item.Rank = rankCounter;
        }

        return orderedList.ToList();
    }
}



海外通貨の種類が全然足りなかったので、手動で追加。

rateの自動生成はどうにでもなりそうだが、prifixからisoの自動生成は難易度が高い。
このレートテーブルが、ホロライブ課金者ランキング を作るときに使ったレート

prifix iso rate memo
ARS ARS 1.48 アルゼンチン・ペソ
A$ AUD 75.54 オーストラリア・ドル
BGN BGN 63.24 ブルガリア・レフ
R$ BRL 20.13 ブラジル・レアル
CA$ CAD 79.45 カナダ・ドル
CHF CHF 115.27 スイス・フラン
CLP CLP 0.14 チリ・ペソ
CZK CZK 4.71 チェコ・コルナ
DKK DKK 16.63 デンマーク・クローネ
EUR 122.8 ユーロ
£ GBP 136.17 UK・ポンド
GTQ GTQ 13.87 グアテマラ・ケツァル
HK$ HKD 13.84 香港・ドル
HRK HRK 16.46 クロアチア・クーナ
HUF HUF 0.36 ハンガリーフォリント
INR 1.43 インド・ルピー
JPY 1 日本・円
KRW 0.09 韓国・ウォン
MX$ MXN 4.78 メキシコ・ペソ
NIO NIO 3.07 ニカラグアコルドバ
NOK NOK 11.59 ノルウェー・クローネ
NZ$ NZD 70.85 ニュージーランド・ドル
PEN PEN 30.46 ペルー・ソル
PHP PHP 2.17 フィリピン・ペソ
PLN PLN 27.6 ポーランドズウォティ
PYG PYG 0.015 パラグアイグアラニー
RON RON 25.37 ルーマニア・レウ
RUB RUB 1.49 ロシア・ルーブル
SEK SEK 12.05 スウェーデン・クローナ
SGD SGD 77.18 シンガポール・ドル
NT$ TWD 3.64 ニュー台湾・ドル
$ USD 107.3 US・ドル
UYU UYU 2.5 ウルグアイ・ペソ
ZAR ZAR 6.41 南アフリカ共和国・ランド