2016-05-03 3 views
0

Я написал алгоритм, который сортирует целочисленные значения в порядке возрастания, но сохраняет соответствующую информацию в смежных ячейках, отсортированную в соответствии с ней. Он использует Arr в качестве моментального снимка сортируемой ячейки, строит массив индексов значений (TagIndex), если они были отсортированы в порядке возрастания, затем применяет TagIndex к этому и соседним ячейкам.Операторы неравенства терпят неудачу при сравнении вариантов

Например, он должен принять это ...

---------------------------------- 
| 5/14/12 | 87 | 91 | 102 | 
| 12/8/11 | 96 | 81 | 93 | 
| 9/30/10 | 75 | 101 | 74 | 
| 4/26/08 | 107 | 95 | 64 | 
---------------------------------- 

... и сортировать по второй колонке крайней левой, чтобы превратить его в это:

---------------------------------- 
| 9/30/10 | 75 | 101 | 74 | 
| 5/4/12 | 87 | 91 | 102 | 
| 12/8/11 | 96 | 81 | 93 | 
| 4/26/08 | 107 | 95 | 64 | 
---------------------------------- 

Вот код:

Dim cell as Range 
Dim Arr, TempArr, BoundVal As Variant 

For Each cell In ActiveSheet.ListObjects("Table2").ListColumns(targetColumn).DataBodyRange 

    Arr = Split(cell.Value, Chr(10)) 
    ReDim TagIndex(0 To UBound(Arr)) As Variant 
    For i = 0 To UBound(Arr) 
     BoundVal = Arr(i) 'starts with first value and index 
     TagIndex(i) = i 'as defaults 
     For j = 0 To UBound(Arr) 
      If Arr(j) < BoundVal Then 'if sorter finds a smaller value, 
       BoundVal = Arr(j)  'flags it... 
       TagIndex(i) = j  '...and its index as smaller, 
      End If      'keeps looking, 
     Next j       'leaves For loop with the smallest, 
     Arr(TagIndex(i)) = 201   'and moves it up out of reach so sorter won't 
    Next i        'flag it anymore (none of the values go above 200) 

    For j = leftBoundColumn To rightBoundColumn 
     TempArr = Split(Cells(cell.Row, j).Value, Chr(10)) 
     For i = 0 To UBound(TempArr) 
      Arr(i) = TempArr(TagIndex(i)) 
     Next i 
     Cells(cell.Row, j).Value = Join(Arr, Chr(10)) 
    Next j 

Next cell 

Этот код работал с dandy сначала, но у меня было две отдельные версии: одна для сортировки целых чисел и другая для сортировки даты - и хотел, чтобы они обрабатывали оба. Для этого я попытался объявить BoundVal как вариант в новом. Когда результаты оказались неустойчивыми, острое использование MsgBox'es показало, что он не прошел логические тесты у оператора <, пытаясь сказать мне «Нет» для 96 < 201 и «Да» для 107 < 75 (но нет для 117/107, как и должно).

Если я вернусь к объявлению BoundVal как целого, он начнет нормально работать для целых чисел, но при попытке использовать его в датах дает ошибку типа «Несоответствие».

Есть ли какая-то фундаментальная проблема при сравнении Arr (j) < BoundVal? Оба варианта, оба из нисходящих строк. Есть идеи?

+0

Просто потому, что вы объявляете переменную, которая будет вариантом, не означает, что она остается такой же, как во время выполнения. Просто проверьте его самостоятельно: 'Dim varTMP As Variant', затем установите его в 20 в следующей строке' varTMP = 20' и, наконец, спросите, какой тип переменной является 'Debug.Print TypeName (varTMP)'. Вы получите ответ «Целое число». Итак, возможно, вы хотите включить еще несколько текстовых полей, чтобы проверить, что вы сравниваете друг с другом ... – Ralph

+0

Спасибо, это проблема. Но BoundVal = CVar (Arr (i)) не преуспевает в изменении BoundVal к варианту. Как еще это можно сделать? –

ответ

0

Вам нужно уйти от всех этих Variant переменных. Этот тип (аналогично объявлению переменной как Object) должен быть вашим последним средством. Variant может быть чем угодно и может даже измениться в ходе кода (как показано выше). Итак, если вы используете их для сравнения с чем-то другим, вы можете в итоге сравнить Boolean (TRUE или FALSE) с Date или String или что-то еще. VBA там, чтобы позволить вам и взять под свой контроль! Поэтому я бы рекомендовал, чтобы вы сделали именно это.

Сказав это и из-за того, что нам не хватает какого-либо кода (очевидно, приведенный выше код является неполным, так как использует некоторые переменные, которые не объявлены и не инициализированы) вот несколько советов о том, как вы можете улучшить свой код и, возможно, сами получите результат:

(1) Если вы хотите объявить несколько переменных в одной строке, вам все равно необходимо повторить тип переменной для каждой переменной. Итак, вам нужно написать

Dim Arr as Variant, TempArr as Variant, BoundVal As Long 

(2) Старайтесь избегать Variant всякий раз, когда это возможно, и использовать соответствующий тип вместо. Например, BoundVal, кажется, сохраняет целые числа или даты, и поэтому тип данных Long может быть лучшим.

(3) Обычно массивы (по умолчанию VBA) варьируются от 0 до ArrayCount - 1.Таким образом, база устанавливается в 0: Option Base 0https://msdn.microsoft.com/en-us/library/aa266179(v=vs.60).aspx Тем не менее, чтобы сделать ваш код дольше (и более прозрачной) я бы включить эту строку в коде и изменить контуры для

For j = LBound(Arr) To UBound(Arr) 

вместо

For j = 0 To UBound(Arr) 

(4) Заставьте себя объявлять все переменные, чтобы убедиться, что вы знаете, что происходит: Option Explicithttps://msdn.microsoft.com/en-us/library/office/gg278855.aspx

(5) Используйте .Value2 всякий раз, когда это возможно, вместо .Value для небольшой бонус скорости. Кроме того, это, по сути, даст вам даты только в виде номера, а не как date2.

После всего вышеперечисленного меняет ваш код должен выглядеть следующим образом:

Option Base 0 
Option Explicit 

Sub tmpSO(Optional targetColumn As Long, Optional leftBoundColumn As Long, Optional rightBoundColumn As Long) 

Dim cell As Range 
Dim Arr As Variant, TempArr As Variant 
Dim BoundVal As Long, TagIndex() As Long, i As Long, j As Long 

'set the defaults for optional parameters 
If targetColumn = 0 Then targetColumn = 2 
If leftBoundColumn = 0 Then leftBoundColumn = 1 
If rightBoundColumn = 0 Then rightBoundColumn = ActiveSheet.ListObjects("Table2").ListColumns.Count 

For Each cell In ActiveSheet.ListObjects("Table2").ListColumns(targetColumn).DataBodyRange 

    Arr = Split(cell.Value2, Chr(10)) 
    ReDim TagIndex(LBound(Arr) To UBound(Arr)) 
    For i = LBound(Arr) To UBound(Arr) 
     'starts with first value and index, use default 0 if necessary 
     If IsDate(Arr(i)) Then    'if the item can be interpreted as a date then 
      BoundVal = CLng(CDate(Arr(i))) 'convert the string to a date and convert the date to a number 
     Else        'otherwise treat it as a number only 
      BoundVal = CLng(IIf(IsNumeric(Arr(i)), Arr(i), 0)) 
     End If 
     TagIndex(i) = i     'as default 
     For j = LBound(Arr) To UBound(Arr) 
      If CLng(IIf(IsDate(Arr(j)), CDate(Arr(j)), Arr(j))) < BoundVal Then  'if sorter finds a smaller value, 
       BoundVal = CLng(IIf(IsDate(Arr(j)), CDate(Arr(j)), Arr(j)))   'flags it... 
       TagIndex(i) = j   '...and its index as smaller, 
      End If       'keeps looking, 
     Next j        'leaves For loop with the smallest, 
     Arr(TagIndex(i)) = 201    'and moves it up out of reach so sorter won't 
    Next i         'flag it anymore (none of the values go above 200) 

    For j = leftBoundColumn To rightBoundColumn 
     TempArr = Split(Cells(cell.Row, j).Value, Chr(10)) 
     For i = 0 To UBound(TempArr) 
      Arr(i) = TempArr(TagIndex(i)) 
     Next i 
     Cells(cell.Row, j).Value2 = Join(Arr, Chr(10)) 
    Next j 

Next cell 

End Sub 

Отказ от ответственности: Я знаю, что этот ответ не является полным и что приведенный выше код, вероятно, нуждается в некоторой настройке, прежде чем он на самом деле делает то, что вы хочу, чтобы это делалось. Тем не менее, код работает без ошибок (проверен в моей системе) и вводит некоторые рекомендации, чтобы вы шли в правильном направлении и (возможно) разрешали все препятствия.

² Все даты и время в VBA - это номера! Даты хранятся как дни между 31 декабря 1899 года и днем, когда вы хотите сохранить время, когда время всегда хранится как десятичные числа, которые составляют часть дня. Итак, 0,5 = полдня = 12 часов. 0,75 = три четверти дня = 6 часов вечера. Сегодняшняя дата - 04 мая 2016 года. Это 42 495 дней после 31 декабря 1899 года. Следовательно, .Value2 на сегодняшний день составляет 42 495.

+0

Спасибо, но 'BoundVal = CLng (IIf (IsNumeric (Arr (i)), Arr (i), 0))' не работает для дат, но еще раз вызывает ошибку несоответствия типа. –

+0

Как написано в моем ответе «Все даты и время в VBA - это числа». И строка 'Arr = Split (cell.Value2, Chr (10))' должна гарантировать, что массив заполняется только числами (используя свойство '.Value2' и ** не ** свойство' .Value'). Но если этот ответ вам не поможет, может быть, кто-то может помочь ... – Ralph

+0

.Value2 не повлиял. Ловушки MsgBox TypeName(), которые у меня есть в моем коде, говорят мне, что Arr (j) - это String, что означает, что 'IsNumeric (Arr (i))' каждый раз возвращает false, и удержание BoundVal упрямо в 0. –

Смежные вопросы