Что нового

Как посчитать количество уникальных записей за один проход. Уже замучался...

vaf

Новичок
Сообщения
190
Репутация
2
Хелп. Казалось бы простая задача, но замучался решать. Деградирую наверное.
В общем дан текстовый файл с IP адресами (примерно 80 тыс. строк)
пример:
192.168.121.88
192.168.121.88
192.168.58.67
192.168.27.80
192.168.5.19
192.168.5.19
192.168.121.88
192.168.58.67 и т.д.
нужно посчитать количество каждых уникальных строк (IP адресов), т.е.
192.168.121.88 - 3
192.168.58.67 - 2
192.168.27.80 - 1
192.168.5.19 - 2 и т.д.
пробовал сначала брать первую строку, пройтись по всем остальным, подсчитать кол-во, потом вторую, если она еще не бралась так же считать все... но это нужно будет на каждую строку проходить весь файл, а это 80 000 записей, в общем это будет на неделю. пробовал поместить все IP адреса в массив, но массив из 80 000 индексов, это просто комп. виснет.
Решил сделать так: берем 1ю строку, заносим ее в массив скажем ipaddr[1] = "192.168.121.88" в параллельный массив пишем
nnaddr[1] = 1, берем вторую строку, производим ее поиск в массиве, если такая нашлась, то nnaddr[1] += 1 если не нашлась значит она уникальна и значит добавляем элемент массива
_ArrayAdd ($ipaddr,"192.168.58.67"), а в параллельный nnaddr[2] = 1
и так далее. вся сложность в том, что не могу реализовать - наверное я заработался или уже голова от недосыпаний не варит.
может кто подскажет как реализовать, запутался уже...
заранее спасибо.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,673
Репутация
2,486
Код:
#include <Array.au3>

$sList = StringStripCR(FileRead("IP_List.txt")) & @LF
$aList = StringSplit($sList, @LF)

Dim $aDupsArray[100000][2]

For $i = 1 To $aList[0]
	If StringInStr($sList, $aList[$i] & @LF) Then
		$sList = StringReplace($sList, $aList[$i] & @LF, "")
		
		$aDupsArray[0][0] += 1
		$aDupsArray[$aDupsArray[0][0]][0] = $aList[$i]
		$aDupsArray[$aDupsArray[0][0]][1] = @extended
	EndIf
Next

ReDim $aDupsArray[$aDupsArray[0][0]+1][2]
_ArrayDisplay($aDupsArray)
 

Garrett

Модератор
Локальный модератор
Сообщения
3,999
Репутация
967
Ещё один вариант :smile:
Код:
#include <Array.au3>
$hIP = FileOpen("ip.txt", 0)

If $hIP = -1 Then
    MsgBox(0, "Error", "Не удается открыть файл.")
    Exit
EndIf

$sIP = FileRead($hIP)
If @error = -1 Then Exit
$aIP = StringSplit(StringStripCR($sIP), @LF)
_ArrayDelete($aIP, 0)
$aUniqueIP = _ArrayUnique($aIP)
_ArrayDelete($aUniqueIP, 0)

$hReport = FileOpen("report.txt", 2)

For $i = 0 To UBound($aUniqueIP)-1
    $aResult = _ArrayFindAll($aIP, $aUniqueIP[$i])
    FileWriteLine($hReport, $aUniqueIP[$i] & " - " & UBound($aResult) & @CRLF)
Next

FileClose($hIP)
FileClose($hReport)
 

kzru_hunter

Осваивающий
Сообщения
144
Репутация
49
С помощью Assign будет намного быстрее сверяться (~350мс для слабого компа с кол-вом записей 10000).

Вот пример:

Код:
$hIpFile = FileRead("ip.txt")
$array = StringSplit($hIpFile, @CRLF, 1)
$count = 0

For $i=1 to $array[0]
	$array[$i] = StringStripWS($array[$i], 3)
	If not Eval("__ip_" & $array[$i]) Then $count += 1
	Assign("__ip_" & $array[$i], $i, 1)
Next

MsgBox(0,"", "$count = " & $count)
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,673
Репутация
2,486
Garrett [?]
Ещё один вариант
Очень медленный.

kzru_hunter [?]
С помощью Assign будет намного быстрее сверяться
Но результат совсем не тот, где тут показано сколько раз дублируется каждый ip?

P.S
Оказывается, через Assign можно присвоить переменную вида $Var.1 :blink:
 

Garrett

Модератор
Локальный модератор
Сообщения
3,999
Репутация
967
CreatoR [?]
Очень медленный.
Да, заметил. Ещё интересно и то, что как ваш вариант, так и мой, на слабых машинах при 80 тыс. строк загружает процессор на 100%! :(
 

kzru_hunter

Осваивающий
Сообщения
144
Репутация
49
CreatoR
Так ему же надо всего лишь подсчитать кол-во уникальных записей и не больше.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Ну, если у него все IP начинаются с "192.168.", то можно так (cчитает почти мгновенно!):

Код:
#Include <Array.au3>

$Timer = TimerInit()

$aList = StringSplit(StringStripCR(FileRead('IP_List.txt')), @LF)

Dim $aIP[256][256]

For $i = 1 To $aList[0]
	$aData = StringSplit($aList[$i], '.')
	If $aData[0] > 3 Then
		$aIP[Number($aData[3])][Number($aData[4])] += 1
	EndIf
Next
$sData = ''
For $i = 0 To 255
	For $j = 0 To 255
		If $aIP[$i][$j] Then
			$sData &= '192.168.' & $i & '.' & $j & ' - ' & $aIP[$i][$j] & '|'
		EndIf
	Next
Next
$aData = StringSplit(StringTrimRight($sData, 1), '|', 2)

ConsoleWrite(TimerDiff($Timer) & @CR)

_ArrayDisplay($aData)

Если все 4 цифры в IP произвольные, то можно так:

Код:
#Include <Array.au3>

$Timer = TimerInit()

$aList = StringSplit(StringStripCR(FileRead('IP_List.txt')), @LF)

For $i = 1 To $aList[0]
	$aNumb = StringSplit($aList[$i], '.')
	If $aNumb[0] > 3 Then
		$aList[$i] = BitShift(Number($aNumb[1]), -24) + BitShift(Number($aNumb[2]), -16) + BitShift(Number($aNumb[3]), -8) + Number($aNumb[4])
	Else
		$aList[$i] = 0
	EndIf
Next

Dim $aData[$aList[0] + 1][2] = [[0]]

For $i = 1 To $aList[0]
	If $aList[$i] Then
		For $j = 1 To $aData[0][0]
			If $aData[$j][0] = $aList[$i] Then
				$aData[$j][1] += 1
				ContinueLoop 2
			EndIf
		Next
		$aData[0][0] += 1
		$aData[$aData[0][0]][0] = $aList[$i]
		$aData[$aData[0][0]][1] = 1
	EndIf
Next

ReDim $aData[$aData[0][0] + 1][2]

For $i = 1 To $aData[0][0]
	$aData[$i][0] = BitAND(BitShift($aData[$i][0], 24), 255) & '.' & BitAND(BitShift($aData[$i][0], 16), 255) & '.' & BitAND(BitShift($aData[$i][0], 8), 255) & '.' & BitAND($aData[$i][0], 255)
Next

ConsoleWrite(TimerDiff($Timer) & @CR)

_ArrayDisplay($aData)

Тоже самое, но только с применением WinAPI:

Код:
#Include <Array.au3>

$Timer = TimerInit()

$aList = StringSplit(StringStripCR(FileRead('IP_List.txt')), @LF)

For $i = 1 To $aList[0]
    $aRet = DllCall('ws2_32.dll', 'ulong', 'inet_addr', 'str', $aList[$i])
    If Not @error Then
        $aList[$i] = $aRet[0]
    Else
        $aList[$i] = 0
    EndIf
Next

Dim $aData[$aList[0] + 1][2] = [[0]]

For $i = 1 To $aList[0]
    If $aList[$i] Then
        For $j = 1 To $aData[0][0]
            If $aData[$j][0] = $aList[$i] Then
                $aData[$j][1] += 1
                ContinueLoop 2
            EndIf
        Next
        $aData[0][0] += 1
        $aData[$aData[0][0]][0] = $aList[$i]
        $aData[$aData[0][0]][1] = 1
    EndIf
Next

ReDim $aData[$aData[0][0] + 1][2]

For $i = 1 To $aData[0][0]
    $aRet = DllCall('ws2_32.dll', 'str', 'inet_ntoa', 'ulong', $aData[$i][0])
    If Not @error Then
        $aData[$i][0] = $aRet[0]
    EndIf
Next

ConsoleWrite(TimerDiff($Timer) & @CR)

_ArrayDisplay($aData)

Результаты (файл в аттаче):

Способ 1 - 0.25 (!) сек
Способ 2 - 44/48 сек
Способ CreatoR'а - 96 сек
 

amel27

Продвинутый
Сообщения
146
Репутация
55
Garrett
как ваш вариант, так и мой, на слабых машинах при 80 тыс. строк загружает процессор на 100%
для одноядерных CPU так и должно быть... ну, если конечно мы хотим подсчитать побыстрее... :smile:

CreatoR
результат совсем не тот, где тут показано сколько раз дублируется каждый ip?
ну это можно подправить... ;)

Код:
Local $s="", $a=StringRegExp(FileRead("ip.txt"),"\S++",3)

For $i=0 to UBound($a)-1
	Assign($a[$i], Eval($a[$i])+1)
	If Eval($a[$i])=1 Then $s&= $a[$i] &@TAB&"$"& $a[$i] &"$"&@CRLF
Next

$ExpandVarStrings = Opt("ExpandVarStrings",1)
ConsoleWrite($s &@CRLF)
Opt("ExpandVarStrings", $ExpandVarStrings)
 

Redline

AutoIT Гуру
Сообщения
506
Репутация
375
Мой вариант
Код:
#include <Array.au3>
$Timer = TimerInit()

$aList_1 = StringSplit(StringStripCR(FileRead('iplist.txt')), @LF)
$oDict = ObjCreate('Scripting.Dictionary')

For $i = 1 To $aList_1[0]
	$oDict.Item($aList_1[$i]) = $oDict.Item($aList_1[$i]) + 1
Next

Dim $aList_2[$oDict.Count][2], $iCounter = 0
For $oKey In $oDict
	$aList_2[$iCounter][0] = $oKey
	$aList_2[$iCounter][1] = $oDict.Item($oKey)
	$iCounter += 1
Next

ConsoleWrite(TimerDiff($Timer) & @LF)
_ArrayDisplay($aList_2)
 
Автор
V

vaf

Новичок
Сообщения
190
Репутация
2
CreatoR - большой респект, элегантно реализовано. Взял для примера файл из 20 тыс. строк, обработал оч.быстро

Redline
_FileReadToArray('iplist.txt', $aList_1)
я уже пробовал вариант с массивами, не дождался пока он 80000 записей в массив затолкает, это для мощных компов :smile:
 

Redline

AutoIT Гуру
Сообщения
506
Репутация
375
vaf [?]
RedlineЦитата
_FileReadToArray('iplist.txt', $aList_1)
я уже пробовал вариант с массивами, не дождался пока он 80000 записей в массив затолкает, это для мощных компов
_FileReadToArray('iplist.txt', $aList_1) равноценна
Код:
$sList = StringStripCR(FileRead("IP_List.txt")) & @LF
$aList = StringSplit($sList, @LF)

А тормоза возможны из-за
Код:
_ArrayDisplay($aList_1)
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,673
Репутация
2,486
Yashied [?]
Способ CreatoR'а - 96 сек
Я не знаю как у тебя такое получилось, но у меня с твоим файлом результат другой (36870.3000690993).
 

amel27

Продвинутый
Сообщения
146
Репутация
55
:gathering:
всё равно наш быстрее и короче :blum:
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,673
Репутация
2,486
amel27 [?]
ну это можно подправить
Ухты!

Вот получился самый быстрый вариант с требуемым результатом:

Код:
#Include <Array.au3>

$Timer = TimerInit()

;=================================================================================================
$aList = StringSplit(StringStripCR(StringStripWS(FileRead(@DesktopDir & "\ip_list.txt"), 3)), @LF)
$sList = ""

For $i = 1 To $aList[0]
    Assign($aList[$i], Eval($aList[$i])+1)
	
    If Eval($aList[$i]) = 1 Then
		$sList &= $aList[$i] & "|$" & $aList[$i] & "$" & @LF
	EndIf
Next

$iOpt_EVS = Opt("ExpandVarStrings", 1)
$aList = StringSplit(StringStripWS($sList, 2), @LF)
Opt("ExpandVarStrings", $iOpt_EVS)

Dim $aDupsArray[$aList[0]+1][2] = [[$aList[0]]]

For $i = 1 To $aList[0]
	$aSplit = StringSplit($aList[$i], "|")
	$aDupsArray[$i][0] = $aSplit[1]
	$aDupsArray[$i][1] = $aSplit[2]
Next
;=================================================================================================

ConsoleWrite(TimerDiff($Timer) & @LF)
_ArrayDisplay($aDupsArray)
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
CreatoR сказал(а):
Я не знаю как у тебя такое получилось, но у меня с твоим файлом результат другой (36870.3000690993).

У меня 4 ядра и, как следствие, 25% загрузки.
 

Garrett

Модератор
Локальный модератор
Сообщения
3,999
Репутация
967
kzru_hunter Отличное решение!
amel27 Хорошо скорректировано!
:ok:

IMHO вердикт.
Великолепное решение поставленной проблемы в данной теме!
Всё генеральное просто :smile:
Ноутбук 1.5 GHz 256 Mb результат скрипта ниже (на основе решений от kzru_hunter и amel27) составляет 3 сек для 80 000 строк CPU 100%
Код:
$Timer = TimerInit()

$hList = FileOpen("ip.txt", 0)
If $hList = -1 Then
    MsgBox(0, "Error", "Не удается открыть файл.")
    Exit
EndIf

$hReport = FileOpen("report.txt", 2)

$sList = FileRead($hList)
$aList = StringSplit(StringStripCR(StringStripWS($sList, 3)), @LF)
$sList = _UniqueValues($aList)

FileWrite($hReport, $sList)

FileClose($hList)
FileClose($hReport)
ConsoleWrite(Round(TimerDiff($Timer)/1000) & @LF)


Func _UniqueValues($a_Values)
    
    Local $sList = ""
    If Not IsArray($a_Values) Then Return SetError(1, 0, 0)

    For $i = 1 To UBound($a_Values) -1
        Assign($a_Values[$i], Eval($a_Values[$i])+1)
        If Eval($a_Values[$i]) = 1 Then
            $sList &= $a_Values[$i] & "|" & "$" & $a_Values[$i] & "$" & @CRLF
        EndIf
    Next

    $ExpandVarStrings = Opt("ExpandVarStrings",1)
        Return SetError(0, 0 , $sList)
    Opt("ExpandVarStrings", $ExpandVarStrings)
    
EndFunc


Однако я тоже был удивлен, как и CreatoR [?]
Оказывается, через Assign можно присвоить переменную вида $Var.1
в отношение Assign :blink: Интересный факт, упрощающий многие решения :smile:


Добавлено:
Сообщение автоматически объединено:

Ноутбук 1.5 GHz 256 Mb
Yashied
1 способ 3.647 сек. CPU 100%
2 способ terminated time out 5 min CPU 100%
3 способ terminated time out 5 min CPU 100%

CreatoR
1 способ terminated time out 5 min CPU 100%

Redline
1 способ 5 сек. CPU 100%
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,673
Репутация
2,486
Yashied [?]
У меня 4 ядра и, как следствие, 25% загрузки.
Ты хочешь сказать, что 4 ядерный процессор отрабатывает медленнее чем один разделённый на двое (у меня так :smile:)?
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
CreatoR сказал(а):
Yashied [?]
У меня 4 ядра и, как следствие, 25% загрузки.
Ты хочешь сказать, что 4 ядерный процессор отрабатывает медленнее чем один разделённый на двое (у меня так :smile:)?

Да. У меня на QX9650 получается медленнее, чем тоже самое на каком-то P4 c HyperThreading и с меньшей тактовой частотой. Факт. Возможно еще сказалась Windows 7...
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,673
Репутация
2,486
Yashied [?]
Хм, только что проверил на ноутбуке с процессором i3 (под Win7), действительно там медленнее, мой скрипт отработал 143 секунды, а твой 63.

Это ставит под вопрос оптимизацию скриптов. Я надеюсь это связанно только с Win7, хотя в любом случае получается несхожесть, ведь на том же Pentium Dual Core (под WinXP) мой пример оказался быстрее немного, и как теперь знать, для какой системы/машины какой использовать метод оптимизаций?
 
Верх