如何使用星号制作垂直直方图?

How do i make a vertical histogram using asterisks?

我正在尝试解决名为 Simple Fun #358: Vertical Histogram Of Letters : https://www.codewars.com/kata/59cf0ba5d751dffef300001f/javascript

的 codewars 问题

总而言之,他们给出了一个字符串输入,所以我必须搜索可能的大写字母 [A-Z],所以如果我找到一个,那么我必须计算它在字符串中出现的次数。之后,我必须使用 asteriks 制作一个垂直直方图,我找到的每个字母都有一个垂直条,高度或长度基于字母出现的时间。

示例:AAABBC

*
* * 
* * *
A B C

但是当我试图解决这个问题时,我得到了这个:

*
* 
*
A *
*
*
B *
C

这是我尝试过的:

        public static string VerticalHistogramOf(string s)
        {
            string alphabet = "ABCDEFGHIJKLMNOPQRSTUWXYZ";
            var dic = new Dictionary<char, int>();
            string histogram = "";

            foreach (var a in alphabet)
            {
                if (s.Contains(a))
                    dic.Add(a, s.Count(x => x == a));
            }

            foreach (var item in dic)
            {
                string bar = "";
                for (int i = 0; i < item.Value; i++)
                {
                    bar += "*\n";
                }
                bar += item.Key + " ";
                histogram += bar;
            }

            return histogram;
        }

那么制作这样的直方图的算法是什么?我想用最简单可读的方式

这很有趣...我想我会使用 LINQy 方法尝试一下:

static void Main(string[] args)
{
    string s = "XXY YY ZZZ123ZZZ AAA BB C";
    string histogram = VerticalHistogram(s);
    Console.Write(histogram);

    Console.Write("Press Enter to Quit.");
    Console.ReadLine();
}

private static string VerticalHistogram(string s)
{
    StringBuilder sb = new StringBuilder();

    // this builds a "dictionary" like you did, but using LINQ
    // it first only lets upper case letters pass through
    // then it groups by those letters and stores the letter with its count
    // then it sorts by the letter
    var letterCounts =
        s.Where(c => Char.IsUpper(c))
        .GroupBy(c => c, (key, element) => new
        {
            Letter = key,
            Count = element.Count()
        })
        .OrderBy(g => g.Letter);

    // this gets the maximum count of all the letters, 
    // which is the max height of our histogram
    var maxHeight = letterCounts.Max(g => g.Count);

    // this generates the numbers from 1 to maxHeight,
    // then reverses the list.
    // next we take each height value going from largest to smallest,
    // and we ask each letter count if it is larger than that value or not.
    // this generates an array of "*" or " " based on the current height
    // and each letter count
    // we put that array together with String.Join() to make up the row
    // for the current height and add it to our StringBuilder
    Enumerable.Range(1, maxHeight).Reverse().ToList().ForEach(h =>
        sb.AppendLine(String.Join(" ", letterCounts.Select(g => g.Count >= h ? "*" : " ")))
    );

    // this creates a row with all the letters for the bottom of the histogram
    sb.AppendLine(String.Join(" ", letterCounts.Select(g => g.Letter)));

    return sb.ToString();
}

生成以下输出:

          *
          *
          *
*       * *
* *   * * *
* * * * * *
A B C X Y Z
Press Enter to Quit.

作为一种变体,您可以在 Enumerable.Range() 调用中制作整个直方图,如下所示:

Enumerable.Range(0, maxHeight).Reverse().ToList().ForEach(h =>
    sb.AppendLine(String.Join(" ", letterCounts.Select(g => 
        (h==0) ? g.Letter.ToString() : 
        (g.Count >= h) ? "*" : " ")))
);

那你就不用单独把字母加到最后一行了。

这是一个四线:

var data = "AAABBC";
var lookup = data.ToLookup(x => x);
var height = lookup.Max(x => x.Count());
var histogram =
    String.Join(
        Environment.NewLine,
        Enumerable
            .Range(0, height)
            .Reverse()
            .Select(x =>
                String.Concat(
                    lookup
                        .SelectMany(y =>
                            y
                                .Select(z => "*")
                                .Concat(Enumerable.Repeat(" ", height - 1))
                                .Skip(x)
                                .Take(1))))
            .Concat(new[] { String.Concat(lookup.Select(x => x.Key)) }));

这给了我:

*  
** 
***
ABC

使用 data = "AAABBCCCCC"; 我得到:

  *
  *
* *
***
***
ABC

这是另一种不是“LINQy”的方法,更类似于您正在做的事情:

private string VerticalHistogram_non_LINQy(string s)
{
    SortedDictionary<char, int> letterCounts = new SortedDictionary<char, int>();
    foreach(char c in s)
    {
        if(Char.IsUpper(c))
        {
            if (!letterCounts.ContainsKey(c))
            {
                letterCounts.Add(c, 1);
            }
            else
            {
                letterCounts[c]++;
            }
        }                
    }

    int? maxHeight = null;
    foreach (KeyValuePair<char, int> kvp in letterCounts)
    {
        if (!maxHeight.HasValue)
        {
            maxHeight = kvp.Value;
        }
        else if (kvp.Value > maxHeight)
        {
            maxHeight = kvp.Value;
        }
    }

    StringBuilder sb = new StringBuilder();
    if (maxHeight.HasValue)
    {
        for (int h=maxHeight.Value; h>0; h--)
        {
            StringBuilder row = new StringBuilder();
            foreach (KeyValuePair<char, int> kvp in letterCounts)
            {
                row.Append((row.Length>0 ? " " : "") + (kvp.Value>=h ? "*" : " "));
            }
            sb.AppendLine(row.ToString());
        }
        sb.AppendLine(String.Join(" ", letterCounts.Keys));
    }
    return sb.ToString();
}