-1

API から 2 つの CSV を取得しています。1 つは次のstudents.csvように呼び出されます。

StudentNo,PreferredFirstnames,PreferredSurname,UPN
111, john, smith, john@email.com
222, jane, doe, jane@email.com

と呼ばれるものrooms.csv

roomName, roomNo, students
room1, 1, {@{StudentNo=111; StudentName=john smith; StartDate=2018-01-01T00:00:00; EndDate=2018-07-06T00:00:00},....
room2, 2,{@{StudentNo=222; StudentName=jane doe; StartDate=2018-01-01T00:00:00; EndDate=2018-07-06T00:00:00},...   

rooms.csv の 3 列目は、API によって提供される配列です。

2つを統合する最良の方法は何ですか

StudentNo,PreferredFirstnames,PreferredSurname,UPN, roomName
111, john, smith, john@email.com, room1
222, jane, doe, jane@email.com, room2

私は次のようなことを考えています...

$rooms = Import-Csv rooms.csv
$students  = Import-Csv students.csv
$combined = $students | select-object StudentNo,PreferredSurname,PreferredFirstnames,UPN,
@{Name="roomName";Expression={ ForEach ($r in $rooms) {
    if ($r.Students.StudentNo.Contains($_.StudentNo) -eq "True") 
{return $r.roomName}}}} 

これはforeachうまくいきますが、私は物事を混同しているのですか、それとももっと効率的な方法がありますか???

--- 元の投稿 ---

このすべての情報を使用して、生徒のデータを比較し、AzureAD を更新してから、AzureAD から取得したfirst namelast nameupnroomおよびその他のデータのリストをコンパイルする必要があります。

私の問題は「効率」です。ほとんど動作するコードがありますが、実行に数時間かかります。現在、私はループしてstudents.csvから、各学生がループしrooms.csvて自分がいる部屋を見つけ、明らかにこれらすべての間に複数の API 呼び出しを待っています。

各学生の部屋を見つける最も効率的な方法は何ですか? カスタムとして CSV をインポートすることは、PSObjectハッシュ テーブルを使用することに匹敵しますか?

4

1 に答える 1

0

提案されたコードを機能させることができましたが、コードとデータを微調整する必要があります。

  • studentsの列rooms.csvをオブジェクトのコレクションに逆シリアル化する追加の手順が必要です。ScriptBlockの配列に評価される のように見えますがHashTable、CSV 入力へのいくつかの変更がまだ必要です。
    • およびプロパティを引用符で囲み、 にキャストする必要がありStartDateます。EndDate[DateTime]
    • 少なくとも複数の学生がいる部屋では、値を引用符で囲む必要があるため、分離された配列要素が追加の列としてImport-Csv解釈されません。,
  • CSV を中間形式として使用することの欠点は、元のプロパティ タイプが失われることです。すべてが[String]インポート時になります。効率化のために元の型にキャストし直すことが望ましい場合もあれば、特定の操作を機能させるために絶対に必要な場合もあります。これらのプロパティを使用するたびにキャストすることもできますが、インポートの直後に一度キャストすることをお勧めします。

それらの変更によりrooms.csv...

roomName, roomNo, students
room1, 1, "{@{StudentNo=111; StudentName='john smith'; StartDate=[DateTime] '2018-01-01T00:00:00'; EndDate=[DateTime] '2018-07-06T00:00:00'}}"
room2, 2, "{@{StudentNo=222; StudentName='jane doe'; StartDate=[DateTime] '2018-01-01T00:00:00'; EndDate=[DateTime] '2018-07-06T00:00:00'}}"

...そしてスクリプトは...

# Replace the [String] property "students" with an array of [HashTable] property "Students"
$rooms = Import-Csv rooms.csv `
    | Select-Object `
        -ExcludeProperty 'students' `
        -Property '*', @{
            Name = 'Students'
            Expression = {
                $studentsText = $_.students
                $studentsScriptBlock = Invoke-Expression -Command $studentsText
                $studentsArray = @(& $studentsScriptBlock)

                return $studentsArray
            }
        }
# Replace the [String] property "StudentNo" with an [Int32] property of the same name
$students = Import-Csv students.csv `
    | Select-Object `
        -ExcludeProperty 'StudentNo' `
        -Property '*', @{
            Name = 'StudentNo'
            Expression = { [Int32] $_.StudentNo }
        }
$combined = $students `
    | Select-Object -Property `
        'StudentNo', `
        'PreferredSurname', `
        'PreferredFirstnames', `
        'UPN', `
        @{
            Name = "roomName";
            Expression = {
                foreach ($r in $rooms)
                {
                    if ($r.Students.StudentNo -contains $_.StudentNo)
                    {
                        return $r.roomName
                    }
                }

                #TODO: Return text indicating room not found?
            }
        }

これが遅くなる理由は、すべての学生オブジェクトに対して線形検索を実行しているためです。実際にはそのうちの 2 つです。最初に部屋のコレクション ( foreach)、次に各部屋の生徒のコレクション( ) を調べ-containsます。現在の生徒が割り当てられていないすべての部屋で、その生徒の部屋が見つかるまで、その部屋の生徒のコレクション全体を何度も繰り返しているため、これはすぐに多くの反復と等価比較に変わります。

線形検索を実行するときに実行できる簡単な最適化の 1 つは、検索する項目を並べ替えることです (この場合、プロパティは各学生Studentsのプロパティによって順序付けられます)...StudentNo

# Replace the [String] property "students" with an array of [HashTable] property "Students"
$rooms = Import-Csv rooms.csv `
    | Select-Object `
        -ExcludeProperty 'students' `
        -Property '*', @{
            Name = 'Students'
            Expression = {
                $studentsText = $_.students
                $studentsScriptBlock = Invoke-Expression -Command $studentsText
                $studentsArray = @(& $studentsScriptBlock) `
                    | Sort-Object -Property @{ Expression = { $_.StudentNo } }

                return $studentsArray
            }
        }

...そして、同じコレクションを検索しているときに、検索しているアイテムよりも大きなアイテムに出くわした場合、コレクションの残りの部分に検索しているものが含まれていない可能性があることがわかります。検索をすぐに中止できます...

@{
    Name = "roomName";
    Expression = {
        foreach ($r in $rooms)
        {
            # Requires $room.Students to be sorted by StudentNo
            foreach ($roomStudentNo in $r.Students.StudentNo)
            {
                if ($roomStudentNo -eq $_.StudentNo)
                {
                    # Return the matched room name and stop searching this and further rooms
                    return $r.roomName
                }
                elseif ($roomStudentNo -gt $_.StudentNo)
                {
                    # Stop searching this room
                    break
                }

                # $roomStudentNo is less than $_.StudentNo; keep searching this room
            }
        }

        #TODO: Return text indicating room not found?
    }
}

さらに良いことに、並べ替えられたコレクションを使用すると、線形検索よりも高速なバイナリ検索を実行することもできます*ArrayクラスはすでにBinarySearch静的メソッドを提供しているため、これも少ないコードで実現できます...

@{
    Name = "roomName";
    Expression = {
        foreach ($r in $rooms)
        {
            # Requires $room.Students to be sorted by StudentNo
            if ([Array]::BinarySearch($r.Students.StudentNo, $_.StudentNo) -ge 0)
            {
                return $r.roomName
            }
        }

        #TODO: Return text indicating room not found?
    }
}

ただし、この問題にアプローチする方法は、部屋への[HashTable]マッピングを使用することです。StudentNoを構築するには少し前処理が必要です[HashTable]が、これにより、学生の部屋を取得するときに一定時間のルックアップが提供されます。

function GetRoomsByStudentNoTable()
{
    $table = @{ }

    foreach ($room in $rooms)
    {
        foreach ($student in $room.Students)
        {
            #NOTE: It is assumed each student belongs to at most one room
            $table[$student.StudentNo] = $room
        }
    }

    return $table
}

# Replace the [String] property "students" with an array of [HashTable] property "Students"
$rooms = Import-Csv rooms.csv `
    | Select-Object `
        -ExcludeProperty 'students' `
        -Property '*', @{
            Name = 'Students'
            Expression = {
                $studentsText = $_.students
                $studentsScriptBlock = Invoke-Expression -Command $studentsText
                $studentsArray = @(& $studentsScriptBlock)

                return $studentsArray
            }
        }
# Replace the [String] property "StudentNo" with an [Int32] property of the same name
$students = Import-Csv students.csv `
    | Select-Object `
        -ExcludeProperty 'StudentNo' `
        -Property '*', @{
            Name = 'StudentNo'
            Expression = { [Int32] $_.StudentNo }
        }
$roomsByStudentNo = GetRoomsByStudentNoTable
$combined = $students `
    | Select-Object -Property `
        'StudentNo', `
        'PreferredSurname', `
        'PreferredFirstnames', `
        'UPN', `
        @{
            Name = "roomName";
            Expression = {
                $room = $roomsByStudentNo[$_.StudentNo]
                if ($room -ne $null)
                {
                    return $room.roomName
                }

                #TODO: Return text indicating room not found?
            }
        }

$roomsByStudentNo搬入と同時に行うことで建物の当たりを改善することができrooms.csvます...

# Replace the [String] property "students" with an array of [HashTable] property "Students"
$rooms = Import-Csv rooms.csv `
    | Select-Object `
        -ExcludeProperty 'students' `
        -Property '*', @{
            Name = 'Students'
            Expression = {
                $studentsText = $_.students
                $studentsScriptBlock = Invoke-Expression -Command $studentsText
                $studentsArray = @(& $studentsScriptBlock)

                return $studentsArray
            }
        } `
    | ForEach-Object -Begin {
        $roomsByStudentNo = @{ }
    } -Process {
        foreach ($student in $_.Students)
        {
            #NOTE: It is assumed each student belongs to at most one room
            $roomsByStudentNo[$student.StudentNo] = $_
        }

        return $_
    }

*小さいアレイを除く

于 2018-09-13T22:53:38.453 に答える