如何确定数组中哪个数字最常出现?

How to determine which number occurs most often in an array?

在用 100 个介于 1 和 11 之间的随机值填充数组后,如何确定哪个值出现次数最多?

这是一个示例代码:

procedure TForm1.Button1Click(Sender: TObject);

  function Calculate: Integer;
  var
    Numbers: array [1..100] of Byte;
    Counts: array [1..11] of Byte;
    I: Byte;
  begin
    // Fill the array with random numbers
    for I := Low(Numbers) to High(Numbers) do
      Numbers[I] := Random(11) + 1;
    // Count the occurencies
    ZeroMemory(@Counts, SizeOf(Counts));
    for I := Low(Numbers) to High(Numbers) do
      Inc(Counts[Numbers[I]]);
    // Identify the maximum
    Result := Low(Counts);
    for I := Low(Counts) + 1 to High(Counts) do
      if Counts[I] > Counts[Result] then
        Result := I;
  end;

begin
  ShowMessage(Calculate.ToString);
end;

It is a simple question [...]

but I can't seem to find any straight answers online.

您不应该搜索解决方案 on-line;相反,您应该开始考虑如何设计能够解决问题的算法。为此,您可能需要笔和纸。

首先,我们需要一些数据来处理:

const
  ListLength = 100;
  MinValue = 1;
  MaxValue = 11;

function MakeRandomList: TArray<Integer>;
begin
  SetLength(Result, ListLength);
  for var i := 0 to High(Result) do
    Result[i] := MinValue + Random(MaxValue - MinValue + 1);
end;

MakeRandomList 函数创建一个动态整数数组。根据需要,数组包含 ListLength = 100 个整数,范围从 MinValue = 1MaxValue = 11

现在,给定这样一个整数列表,

var L := MakeRandomList;

我们如何找到最频繁的值?

好吧,如果我们在没有计算机的情况下仅使用笔和纸来解决这个问题,我们可能会计算列表中每个不同值 (1, 2, ..., 11) 出现的次数, 没有?

那么我们只需要找到频率最大的值即可。

例如,给定数据

2, 5, 1, 10, 1, 5, 2, 7, 8, 5

我们会数数以找到频率

X   Freq
2   2
5   3
1   2
10  1
7   1
8   1

然后我们从顶行到底行读取table,找到频率最高的那一行,不断跟踪当前的赢家。

既然我们知道如何解决这个问题,写一段执行这个算法的代码就很简单了:

procedure FindMostFrequentValue(const AList: TArray<Integer>);
type
  TValueAndFreq = record
    Value: Integer;
    Freq: Integer;
  end;
var
  Frequencies: TArray<TValueAndFreq>;
begin

  if Length(AList) = 0 then
    raise Exception.Create('List is empty.');

  SetLength(Frequencies, MaxValue - MinValue + 1);

  // Step 0: Label the frequency list items

  for var i := 0 to High(Frequencies) do
    Frequencies[i].Value := i + MinValue;

  // Step 1: Obtain the frequencies

  for var i := 0 to High(AList) do
  begin
    if not InRange(AList[i], MinValue, MaxValue) then
      raise Exception.CreateFmt('Value out of range: %d', [AList[i]]);
    Inc(Frequencies[AList[i] - MinValue].Freq);
  end;

  // Step 2: Find the winner

  var Winner: TValueAndFreq;
  Winner.Value := 0;
  Winner.Freq := 0;

  for var i := 0 to High(Frequencies) do
    if Frequencies[i].Freq > Winner.Freq then
      Winner := Frequencies[i];

  ShowMessageFmt('The most frequent value is %d with a count of %d.',
    [Winner.Value, Winner.Freq]);

end;

Delphi有一个TDictionaryclass,可以用来实现频率映射,eg:

uses 
  ..., System.Generics.Collections;

function MostFrequent(Arr: array of Integer) : Integer;
var
  Frequencies: TDictionary<Integer, Integer>;
  I, Freq, MaxFreq: Integer;
  Elem: TPair<Integer, Integer>;
begin
  Frequencies := TDictionary<Integer, Integer>.Create;
  // Fill the dictionary with numbers
  for I := Low(Arr) to High(Arr) do begin
    if not Frequencies.TryGetValue(Arr[I], Freq) then Freq := 0;
    Frequencies.AddOrSetValue(Arr[I], Freq + 1);
  end;  
  // Identify the maximum
  Result := 0; 
  MaxFreq := 0;
  for Elem in Frequencies do begin
    if Elem.Value > MaxFreq then begin
      MaxFreq := Elem.Value;
      Result := Elem.Key;
    end;
  end;
  Frequencies.Free;
end;
var
  Numbers: array [1..100] of Integer;
  I: Integer;
begin
  // Fill the array with random numbers
  for I := Low(Numbers) to High(Numbers) do
    Numbers[I] := Random(11) + 1;
  // Identify the maximum
  ShowMessage(IntToStr(MostFrequent(Numbers)));
end;

我也在学习中,因此觉得我处理这个问题的方式可能更接近于我应该做的方式:

procedure TForm1.GetMostOccuring;
var
  arrNumbers : array[1..100] of Integer;
  iNumberWithMost : Integer;
  iNewAmount, iMostAmount : Integer;
  I, J : Integer;
begin
  for I := 1 to 100 do
    arrNumbers[I] := Random(10) + 1;

  iMostAmount := 0;

  for I := 1 to 10 do
  begin
    iNewAmount := 0;

    for J := 1 to 100 do
      if I = arrNumbers[J] then
        inc(iNewAmount);

    if iNewAmount > iMostAmount then
    begin
      iMostAmount := iNewAmount;
      iNumberWithMost := I;
    end;
  end;

  ShowMessage(IntToStr(iNumberWithMost));
end;

我希望这不是完全没用。 这只是对一个简单问题的简单回答。