【问题标题】:Round integer to nearest high number in array将整数舍入到数组中最接近的大数
【发布时间】:2009-07-17 13:57:08
【问题描述】:

我有一个这样的整数数组:[32,128,1024,2048,4096]

给定一个特定的值,我需要得到数组中等于或大于该值的最接近的值。

我有以下代码

  private int GetNextValidSize(int size, int[] validSizes)
  {

      int returnValue = size;

      for (int i = 0; i < validSizes.Length; i++)
      {
          if (validSizes[i] >= size)
          {
              returnValue = validSizes[i];
              break;
          }
      }

      return returnValue;
  }

它有效,但有没有更好/更快的方法来做到这一点?该数组永远不会包含超过 5-10 个元素。

澄清:如果它大于任何有效尺寸,我实际上想返回原始值/尺寸。可以认为validSizes 数组始终是有序的,并且它始终包含至少一个值。

【问题讨论】:

  • 你的数组元素总是2的幂吗? => 如果是的话,某种位魔法可能是最快的解决方案
  • 此代码的一个潜在问题是该函数不以任何方式指示返回的值是否存在于数组中(因此,如果 size > validSizes 中的最大值,则 GetNextValidSize 返回 size,即实际上是无效的)。

标签: c# arrays


【解决方案1】:

只有 5-10 个元素,绝对是最简单的解决方案。让二进制印章工作将有助于更大的数组,但它至少有潜在错误。

但是,我不会中断,而是直接从循环中返回以使其更简单,并且也使用 foreach:

  private int GetNextValidSize(int size, int[] validSizes)
  {    
      int returnValue = size;

      foreach (int validSize in validSizes)
      {
          if (validSize >= size)
          {
              return validSizes;
          }
      }

      // Nothing valid    
      return size;
  }

您可以使用 LINQ 使这更简单:

// Make sure we return "size" if none of the valid sizes are greater
return validSizes.Concat(new[] { size })
                 .First(validSize => validSize >= size);

如果没有Concat 步骤会更简单...或者如果有一个只采用单个元素的Concat 方法。诚然,这很容易写:

public static IEnumerable<T> Concat(this IEnumerable<T> source,
                                    T tail)
{
    foreach (T element in source)
    {
        yield return element;
    }
    yield return tail;
}

那就是:

return validSizes.Concat(size).First(validSize => validSize >= size);

或者(我意识到我提供的选项比这里真正需要的要多!)FirstOrDefault 的重载,它采用默认值返回:

public static T FirstOrDefault(this IEnumerable<T> source,
                               Func<T, bool> predicate,
                               T defaultValue)
{
    foreach (T element in source)
    {
        if (predicate(element))
        {
            return element;
        }
    }
    return defaultValue;
}

这样称呼它:

return validSizes.FirstOrDefault(validSize => validSize >= size, size);

这两种方法一次性使用都过大了,但如果您已经在构建一个包含额外 LINQ 运算符的库,它可能会很有用。

【讨论】:

    【解决方案2】:

    鉴于您只有 5-10 个元素,我认为这是可以的。

    【讨论】:

    • +1。您可能会找到更快的方法,但在理解方面不会更好。
    • +1 ...而且可能不会更快。渐近更快的算法通常显示更高的常数。如果您的数组限制为 10 个元素,您可以假设它是一个常数时间操作,其中常数是 10 个比较操作,无论如何这不是一个常数(如果您使用平衡树,您最终会得到一个四级别树,最多进行 4 次比较,这并不是什么优势......
    【解决方案3】:
    int[] validSizes = new int[] { 32, 128, 1024, 2048, 4096 };
    
    int sizeICareAbout = 4096;
    
    Console.Write(validSizes.Max(i => i < sizeICareAbout ? i : Int32.MinValue));
    

    如果您输入最小值,这将返回 Int32.MinValue。天哪,我喜欢 LINQ。

    【讨论】:

      【解决方案4】:

      您可以使用 LINQ 来简化查询 - 如果您的列表已排序,它可能会与您编写的任何内容一样快。

      int someInitialValue;
      int defaultIfNotFound = ... // set to some value, even initialValue
      // attempt to find first value less than or equal
      int bestMatch = myListOfValues.Concat( new []{defaultIfNotFound} )
                                    .FirstOrDefault( x => x >= someInitialValue );
      

      如果数组没有排序,或者你需要更好的性能:

      myListOfValues.OrderBy( x => x ).Concat( new []{defaultIfNotFound} )
                                      .FirstOrDefault( x => x >= someInitialValue );
      

      您提到您列出的内容相对较小(5-10 项)-因此线性搜索可能足够快。但是,在更大的列表(数十或数百个项目)上,您可能需要考虑using a binary search 来查找值:

      // index is positive if an exact match is found
      // if no exact match is found, the index returned will be two's complement and
      // reference the next number immediately larger than the search target
      int index = myListOfValues.BinarySearch( someInitialValue );
      if( index < 0 && ~index > myListOfValues.Length )
         bestMatch = someInitialValue;
      else
         bestMatch = index < 0 ? myListOfValues[~index] : myListOfValues[index];
      

      【讨论】:

      • 您需要考虑初始值高于数组中任何值的情况。这使它更丑陋(见我的回答)。
      • 没错,但是,调用者没有定义该案例的预期结果。我会更新我的答案以解决这个问题。
      【解决方案5】:

      它不起作用。这里有 3 个失败的测试用例。事实上,函数接口并没有任何失败的返回结果。

      我写了一个更正的版本,GetNextValidSize2。由于无法返回失败消息,因此我会针对这些情况抛出异常。以下是运行结果:

      test1 : GetNextValidSize 失败 test1 : GetNextValidSize2 通过 test2 : GetNextValidSize 对象引用未设置为对象的实例。 test2 : GetNextValidSize2 validSizes 什么都不是 test3 : GetNextValidSize 已通过 test3 : GetNextValidSize2 validSizes 中没有项目

      顺便说一句,LINQ 可能更简单或更容易,但它几乎不可能更有效。如果查询优化器/CLR 优化器运行良好,它可能同样有效。

      这是代码 - 它在 VB 中,因为这是我目前正在使用的,不想切换思维方式:

      模块模块1

      ''' <summary>
      ''' Error - does not work if validSizes is Nothing, or has 0 elements, or if
      ''' the list contains a validSize that is not the closest one before a closer one,
      ''' or there are no valid sizes.
      ''' </summary>
      Public Function GetNextValidSize(ByVal size As Integer, ByVal validSizes As List(Of Integer)) As Integer
          Dim returnValue As Integer = size
      
          For i As Integer = 0 To validSizes.Count - 1 Step 1
              If validSizes.Item(i) >= size Then
                  returnValue = validSizes.Item(i)
                  Exit For
              End If
          Next
          Return returnValue
      End Function
      
      ''' <summary>
      ''' Returns the closest item in validSizes that is >= size. Throws an exception if one cannot 
      ''' be found.
      ''' </summary>
       Public Function GetNextValidSize2(ByVal size As Integer, ByVal validSizes As List(Of Integer)) As Integer
          Dim closestValue As Integer = Integer.MaxValue
          Dim found As Boolean = False
      
          If validSizes Is Nothing Then
              Throw New Exception("validSizes is nothing")
          End If
      
          If validSizes.Count = 0 Then
              Throw New Exception("No items in validSizes")
          End If
      
          For Each x In validSizes
              If x >= size Then
                  found = True
                  If x < closestValue Then
                      closestValue = x
                  End If
              End If
          Next
          If Not found Then
              Throw New Exception("No items found")
          End If
          Return closestValue
      End Function
      
      ''' <summary>
      ''' Output the result of a test.
      ''' </summary>
       Public Sub outputResult(ByVal testName As String, ByVal result As Boolean, ByVal funcName As String)
          Dim passFail As String
          If result Then
              passFail = " passed"
          Else
              passFail = " failed"
          End If
          Console.WriteLine(testName & " : " & funcName & passFail)
      End Sub
      
      ''' <summary>
      ''' Output the result of a test where an exception occurred.
      ''' </summary>
       Public Sub outputResult(ByVal testName As String, ByVal ex As Exception, ByVal funcName As String)
      
          Console.WriteLine(testName & " : " & funcName & " " & ex.Message())
      End Sub
      
      ''' <summary>
      ''' Test with a list of 3 integers
      ''' </summary>
       Public Sub test1()
          Dim aList As New List(Of Integer)
          aList.Add(5)
          aList.Add(4)
          aList.Add(3)
          Dim result = GetNextValidSize(3, aList)
          outputResult("test1", 3 = GetNextValidSize(3, aList), "GetNextValidSize")
          outputResult("test1", 3 = GetNextValidSize2(3, aList), "GetNextValidSize2")
      End Sub
      
      ''' <summary>
      ''' Test with a null reference
      ''' </summary>
      Public Sub test2()
          Dim aList = Nothing
          Try
              outputResult("test2", GetNextValidSize(3, aList), "GetNextValidSize")
          Catch ex As Exception
              outputResult("test2", ex, "GetNextValidSize")
          End Try
          Try
              outputResult("test2", GetNextValidSize2(3, aList), "GetNextValidSize2")
          Catch ex As Exception
              outputResult("test2", ex, "GetNextValidSize2")
          End Try
      End Sub
      
      ''' <summary>
      ''' Test with an empty array.
      ''' </summary>
      Public Sub test3()
          Dim aList As New List(Of Integer)
          Try
              outputResult("test3", GetNextValidSize(3, aList), "GetNextValidSize")
          Catch ex As Exception
              outputResult("test3", ex, "GetNextValidSize")
          End Try
          Try
              outputResult("test3", GetNextValidSize2(3, aList), "GetNextValidSize2")
          Catch ex As Exception
              outputResult("test3", ex, "GetNextValidSize2")
          End Try
      End Sub
      
      ''' <summary>
      ''' Run all tests.
      ''' </summary>
      Public Sub testAll()
          test1()
          test2()
          test3()
      End Sub
      
      Sub Main()
          testAll()
          Console.ReadLine()
      End Sub
      

      结束模块

      【讨论】:

        【解决方案6】:

        如果您的数组是有序的,您可以使用二分搜索算法加快速度。

        见:http://en.wikipedia.org/wiki/Binary_search_algorithm

        【讨论】:

          【解决方案7】:

          我想你只会得到 first 更大的数字,不一定是 最接近的更大 数字。

          如果您的数组未排序,则需要将其重复传递以找到正确的数字。首先,您会找到最大的,其次是仍然大于原始值的较小值。

          【讨论】:

          • 您不需要两次通过。对于未排序的数组,只需要一次遍历所有元素并保持当前的最佳猜测。
          • 是的,我很怀念它!
          • 没有指定数组是否保证排序。如果已排序,则第一个将是最接近的。如果不是,那么二分查找将不起作用。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-09-27
          • 2011-03-21
          • 1970-01-01
          • 1970-01-01
          • 2013-06-13
          • 1970-01-01
          相关资源
          最近更新 更多