SelectMany

SelectMany 연산자란?

새로운 스트림을 생성하고, 그 스트림에 흐르는 메시지를 본래의 스트림 메시지로 다루는 경우 사용하는 연산자로 다루고자 하는 스트림을 변경할 때 편리하게 사용할 수 있는 연산자입니다.

새로운 스트림을 생성하는 연산자이기 때문에 SelectMany 연산자의 리턴값은 당연히 스트림의 형태가 됩니다.

사용법

세 가지 사용예에 대해서 소개.

목록 평탄화

흔히 리스트(List)로부터 foreach 구문을 사용해서 새로운 목록을 만드는 코드.

// foreach문을 사용, 목록의 리스트를 얻어 오는 방법
List <List <string >> listOfNameList = LoadListOfNameList ();
List <string> nameList = new List <string> ();
foreach (List <string> names in listOfNameList) {
    nameList.AddRange (names);
}

List >를 List 하고 있네요. 이러한 처리는 SelectMany 를 사용하면 보다 간결하게 바꿀 수 있습니다.

SelectMany 목록의 평탄화
List <List <string >> listOfNameList = LoadListOfNameList ();
List <string> nameList = listOfNameList.SelectMany (names => names) .ToList ();

SelectMany를 사용하면 foreach 구문 없이 한줄로 해결할 수 있어요.

이 프로세스는 다른 언어라고 flatten (인수 없음)라는 메소드로 표현 될 수도 있지요.

요소 X의 목록을 가진 개체 Y. Y 목록에서 X의 목록을 만든다

다음 Person 클래스의 목록 (List )에서 모든 사람의 취미 목록 (List )를 만들고 싶다고합니다.

// Person 클래스
public class Person
{
    public string Name {get; set;}
    public List <string> Hobbies {get; set;}
}

우선 foreach 문에 써 봅니다.

// foreach 문에서 목록의 평탄화
List < Person >  persons  =  LoadAllPerson (); 
List < string >  hobbyList  =  new  List < string >  (); 
foreach ( Person  person  in  persons )  { 
    hobbyList . AddRange ( person . Hobbies ); 
}

이를 SelectMany으로 다시 작성합니다.

SelectMany 목록의 평탄화
List < Person >  persons  =  LoadAllPerson (); 
List < string >  hobbyList  =  persons . SelectMany (  person  =>  person . Hobbies  ). ToList ();

foreach 문이 없어져 깔끔한 했어요! ! !

요소 X의 목록을 가진 개체 Y. Y 목록에서 개별 X와 이에 대응하는 Y에서 새로운 개체 Z를 만들고 Z의 목록을 만들

먼저 예에서 사용하는 코드를 보라.

Skill 클래스
public  class  Skill  { 
public  String  Name {  get ;  set ;  } 
    // 최소한. MP 라든지 
}

설명문 X에 대응하는 Skill 클래스입니다.

// Character 클래스
public  class  Character  { 
    public  string  Name  {  get ;  set ;  } 
    public  List < Skill >  Skills  {  get ;  set ;  } 
    // 최소한. MP이나 HP 라든지 진짜라면 더있다. 
}

설명문으로는 Y에 대응하는 Character 클래스입니다. Skill 목록 (요소 X의 목록)와 이름을 나타내는 Name을 가지고 있습니다.

// SkillView 클래스
public  class  SkillView  { 
    public  string  OwnerName  {  get ;  set ;  } 
    public  string  SkillName  {  get ;  set ;  } 
    // 최소한. 실제로는 더 다양하다. 
}

이 SkillView 설명 문중의 Z에 대응합니다. X, Y, Z에 형식을 적용하여하고 싶은 일을 설명합니다. "요소 Skill의 목록을 가진 개체 Character.Character 목록에서 개별 Skill 및 해당 Character에서 새로운 개체 SkillView을 만들고 SkillView의 목록을 만든다" Xamarin를 이용한 응용 프로그램 개발이나 Unity의 게임 개발에서 이러한 작업을하는 것이 아닐까요?

이것도 우선 foreach 문에 써 봅니다.

// foreach 문에서 Character의 List에서 SkillView의 List를 작성 (재게)
List < Character >  characters  =  LoadAllCharacters  ();

List < SkillView >  skillViews  =  new  List < SkillView >  (); 
foreach  ( Character  character  in  characters )  { 
    foreach  ( Skill  skill  in  character . Skills )  { 
        SkillView  skillView  =  new  SkillView  { 
            OwnerName  =  character . Name , 
            SkillName  =   skill . Name 
        } ; 
        skillViews . Add  ( skillView ); 
    } 
}

이중 foreach 문을 사용하여 List 의 characters를 List 의 skillViews로 변환 할 수있었습니다. 긴하네요.

이를 LINQ에서 다시보십시오.

LINQ에서 Character의 List에서 SkillView의 List를 작성 (재게)
List < SkillView >  skillViews  =  characters . SelectMany  ( 
        ( character  =>  character . Skills )  
        ( skillOwner ,  skill )  =>  new  SkillView {  OwnerName  =   skillOwner . Name ,  SkillName  =  skill . Name }) 
. ToList ();

깔끔한 했어요. 시원해졌습니다 만, 초견이라고 "이것은 도대체 뭐야? 뭘하고있는거야?」라고 생각한 분도 것이 아닐까요?

여기까지의 정리

그런데,

  • 목록의 목록을 평탄화하고 목록하기
  • 요소 X의 목록을 가진 개체 Y. Y 목록에서 X의 목록을 만든다
  • 요소 X의 목록을 가진 개체 Y. Y 목록에서 개별 X와 이에 대응하는 Y에서 새로운 개체 Z를 만들고 Z의 목록을 만들

3 개의 예를 구보로 소개시켜 주셨습니다. SelectMany를 "아, 이것은 그 장면에서 사용!」라고 생각하신 분들도있는 것은 없을까요?

단지 여기까지에서 SelectMany의 인수로 전달 된 람다 식의 의미와 처리에 대해 아무것도 언급하지 않았습니다. 사실 같은 SelectMany 메서드도 실은 1,2 번째의 예와 3 번째의 예에서 다른 오버로드를 사용하고 있습니다. 1,2 번째 예는 인수의 대리자가 1 개, 3 번째의 예는 2 개의 었지요. 또한 1 번째의 예는 SelectMany의 인수 names => names라는 람다 식을 대리자로 전달합니다. 전달 된 것을 그대로 전달 람다 식, 이것은 도대체 무슨 의미가있는 것일까 요? 3 번째의 예는 도대체 무엇을 나타내고 있는가?  여기에서 조금 돌진 한 것을 써 가려고합니다. 오이타 길어져 버렸습니다. SelectMany 편리! 여기에서 만족 분들 감사합니다.

각각의 예를 돌진 생각

순서를 바꿔 1 번째의 예 "목록의 목록을 평탄화하고 목록에하는"전에 먼저 "요소 X의 목록을 가진 개체 Y.Y 목록에서 X의 목록을 만든다"를보고 봅니다.

"요소 X의 목록을 가진 개체 Y.Y 목록에서 X의 목록을 만든다」를 돌진 생각

"요소 X의 목록을 가진 개체 Y.Y 목록에서 X의 목록을 만든다"관련 코드를 재게합니다.

// Person 클래스
public class Person
{
    public string Name {get; set;}
    public List <string> Hobbies {get; set;}
}
// SelectMany 목록의 평탄화
List < Person >  persons  =  LoadAllPerson (); 
List < string >  hobbyList  =  persons . SelectMany (  person  =>  person . Hobbies  ). ToList ();

이 예에서 사용하고있는 SelectMany 메서드는 실제로는 Enumerable 클래스의 클래스 메소드 "Enumerable.SelectMany "입니다. 이것은 IEnumerable 형식의 확장 메서드가 있습니다. MSDN의 레퍼런스는 여기 입니다. 여기에서 설명과 구문을 가지고 왔습니다.

설명

시퀀스의 각 요소를 IEnumerable에 투영하고 결과 시퀀스를 단일 시퀀스로 평면화(-flatten)합니다.

구문

public  static  IEnumerable <TResult>  SelectMany <TSource, TResult> ( 
    this  IEnumerable <TSource>  source , 
    Func <TSource, IEnumerable <TResult>>  selector 
)

그런데, 구문을 살펴 보도록하자. 우선 인수 this가 붙어 있네요. 다른 LINQ와 마찬가지로 IEnumerable 의 확장 메서드로 Enumerable 클래스에 정의되어있는 것이군요.

다음 두 번째 인수의 selctor, Func > 형의 대리자입니다. "TSource 형을 받고, IEnumerable 형식을 반환 (투영하는) 대리자"를 넘기면된다 네요.

마지막으로 결과는 IEnumerable 형 네요. 설명에서 읽을에 selector에서 반환 된 결과 (IEnumerable )를 연결하여 하나의 시퀀스 (IEnumerable )로 출력한다 (평평)군요.

위의 예제와 구문을 맞대고 생각해 보겠습니다. TSource과 TReslt 뭔가 인수에 전달할 대리자가 실제로 어떤 처리 한 것입니까? persons는 List 그래서 TSource는 Person 네요.

결과가 IEnumerable 형이므로 (ToList에서 List 에 있습니다), TResult는 string 형 네요. 것은

Func <TSource, IEnumerable <TResult >>

의 selector는 여기에서

Func <Person, IEnumerable <string >>

군요.

SelectMany 인수 람다 식을 일부러 장황하게 쓰고 바꿔 변수에 대입 해 보겠습니다.

//SelectMany 목록의 평탄화를 일부러 장황하게
List < List >  persons  =  LoadAllPerson ();

Func < Person ,  IEnumerable < string >>  selector  =  ( Person  person )  =>  {  
    IEnumerable < string >  hobbies  =  person . Hobbies ; 
    return  hobbies ; 
};

List < string >  hobbyList  =  persons . SelectMany ( selector ). ToList ();

※ 여기의 목록 정확히 IEnumerable 입니다

SelectMany의 인수에 전달할 대리자는

  • 인수는 SelectMany를 호출 목록의 요소의 형태 (이 예에서는 Person)
  • 반환 값은 생성 할 목록 형과 같은 목록 형 (이 예에서는 IEnumerable )

면 좋네요.

"목록 목록 평탄화"을 돌진 해 보면

순서는 전후했지만 "목록 목록 평탄화"라는 예와 SelectMany 구문에서 각각의 형태가 무엇인지 생각해보십시오. 이쪽이 이전 예제보다 약간 복잡합니다. 이쪽도 앞의 예와 같은 오버로드를 사용하고 있습니다. MSDN의 레퍼런스는 여기

// 구문 (재게)
public  static  IEnumerable < TResult >  SelectMany < TSource ,  TResult > ( 
    this  IEnumerable < TSource >  source , 
    Func < TSource ,  IEnumerable < TResult >>  selector 
)
// SelectMany 목록의 평탄화 (재게)
List <List <string >> listOfNameList = LoadListOfNameList ();
List <string> nameList = listOfNameList.SelectMany (names => names) .ToList ();

names => names라는 람다 식은보기 간단하네요.

TSource과 TResult 여기서의 형태는 무엇일까요?

listOfNameList는 List > 그래서 TSource는 List 네요. 여기서주의는 TSouce는 string이 아니라 List 라는 것입니다. 결과가 IEnumerable 형이므로 (ToList에서 List 에 있습니다), TResult는 string 형 네요. 것은

Func <TSource, IEnumerable <TResult >>

같은 selector는 여기에서

Func <List <string> IEnumerable <string >>

군요.

SelectMany 인수 람다 식을 일부러 장황하게 쓰고 바꿔 변수에 대입 해 보겠습니다.

// SelectMany 목록의 평탄화를 일부러 장황하게
List < List < string >>  listOfNameList  =  LoadListOfNameList ();

Func < List < string >  IEnumerable < string >>  selector  =  ( List < string >  list )  =>  { 
    IEnumerable < string >  names  =  list ; 
    return  names ; 
};
List < string >  nameList  =  listOfNameList . SelectMany ( selector ). ToList ();

반복이됩니다 만, TSource가 List 에서 TResult이 string입니다.

SelectMany에 전달 대리자는 TSouce을 전달하고 IEnumerable 를 바꾸어합니다. 이 예제에서는 List 을 전달하고 IEnumerable 을 반환합니다. names => names라는 표기는보기 그대로 전달하고있는만큼 보이지만 형태를 바탕으로 생각하면 꽤 깊 네요.

"요소 X의 목록을 가진 개체 Y.Y 목록에서 개별 X와 이에 대응하는 Y에서 새로운 개체 Z를 만들고 Z의 목록을 만든다」를 돌진 해 보면

이전 두 사건과이 예제는 SelectMany 다른 오버로드를 사용하고 있습니다.

복습

Skill 클래스 (재게)
public  class  Skill  { 
    public  string  Name {  get ;  set ;  } 
    // 최소한. MP 라든지 
}
Character 클래스 (재게)
public  class  Character  { 
    public  string  Name  {  get ;  set ;  } 
    public  List < Skill >  Skills  {  get ;  set ;  } 
    // 최소한. MP이나 HP 라든지 진짜라면 더있다. 
}
SkillView 클래스 (재게)
public  class  SkillView  { 
    public  string  OwnerName  {  get ;  set ;  } 
    public  string  SkillName  {  get ;  set ;  } 
    // 최소한. 실제로는 더 다양하다. 
}
LINQ에서 Character의 List에서 SkillView의 List를 작성 (재게)
List < SkillView >  skillViews  =  characters . SelectMany  ( 
        ( character  =>  character . Skills )  
        ( skillOwner ,  skill )  =>  new  SkillView {  OwnerName  =   skillOwner . Name ,  SkillName  =  skill . Name }) 
. ToList ();

우선 구문을 살펴보면

이 예에서 사용하고있는 SelectMany 메서드는 이전 두 사건과 다른 오버로드입니다. 인수의 대리자 수가 다르군요.

이 예제의 메소드는 Enumerable 클래스의 클래스 메소드 "SelectMany "입니다. 이것도 확장 메서드되어 있습니다. MSDN의 레퍼런스는 여기 입니다. 여기에서 설명과 구문을 가지고 왔습니다.

설명

시퀀스의 각 요소를 IEnumerable에 투영하고 결과 시퀀스를 단일 시퀀스로 평면화 한 다음 포함 된 각 요소에 대해 결과 선택기 함수를 호출합니다.

구문

public  static  IEnumerable < TResult >  SelectMany < TSource ,  TCollection ,  TResult > ( 
    this  IEnumerable < TSource >  source , 
    Func < TSource ,  IEnumerable < TCollection >>  collectionSelector , 
    Func < TSource ,  TCollection ,  TResult >  resultSelector 
)

SelectMany 와 있습니다. 이전 오버로드에 비해 형식 인수 TCollection가 늘고 있군요. 설명문에서 이전 오버로드와 순서를 평탄화하는 것은 같네요. 또한 평탄화 한 중간 요소의 각 요소에 대해 인수로 전달 된 대리자로 투영 할 것 같네요. 장황하게 쓴 코드 예제의 형식을 보면

구문을보기 전에 장황하게 쓴 코드를보고합니다.

// Func <TSource, IEnumerable <TCollection >> 
Func < Character ,  IEnumerable < Skill >>  collectionSelector  =  ( Character  character )  =>  {  
    IEnumerable < Skill >  skills  =  character . Skills ; 
    return  skills ; 
};

// Func <TSource, TCollection, TResult> 
Func < Character ,  Skill ,  SkillView >  resultSelector  =  ( Character  skillOwner ,  Skill  skill )  =>  { 
    SkillView  skillView  =  new  SkillView { 
        OwnerName  =  skillOwner . Name , 
        SkillName  =  skill . Name 
    }; 
    return  skillView ; 
};

List < SkillView >  skillViews  =  characters . SelectMany  ( collectionSelector ,  resultSelector ). ToList  ();

그러면 구문과 비교하여 형식이나 대리자를 살펴 보겠습니다.

구문 (재게)
public  static  IEnumerable < TResult >  SelectMany < TSource ,  TCollection ,  TResult > ( 
    this  IEnumerable < TSource >  source , 
    Func < TSource ,  IEnumerable < TCollection >>  collectionSelector , 
    Func < TSource ,  TCollection ,  TResult >  resultSelector 
)

우선 SelectMany의 반환 값이 예에서는 IEnumerable 네요. (그 후 ToList에서 List 에 있습니다.) TResult 형은 SkillView 네요.

첫 번째 인수에서 IEnumerable 의 확장 메서드라는 것을 알 수 있습니다. 그리고 TSouce 형은 Character 형 네요.

다음 두 번째 인수 collectionSelector는 Func > 형의 대리자 네요. 이것은 이전 오버로드 selector를 닮아 있네요. 이전 오버로드에서 형식 매개 변수가 TResult에서 TCollection으로 변해 있습니다. 이 예제에서는 Character 형을 취해 IEnumerable 을 반환 대리자되어 있습니다. TCollection는 Skill 형 네요. 이 대리자로 한번 평탄화를 중간 요소 (조리 투영하기 전의 상태)를 만드는 것 같네요.

그리고 세 번째 인수 resultSelector. Func 형 네요. TSource 형과 TCollection 형을 받고 TResult을 돌려 준다. 여기에서는 Character과 Skill을 받고, SkillView을 반환 대리자 네요. 두 번째 인수의 collectionSelector 1 회 평탄화 한 중간 결과에 대해이 대리자로 투영하는 것 같네요.

싹둑 말하면

두 번째 전달 인자의 대리자로 평평 (※ 단 다음 투영하기 전에 조리 상태) 세 번째 인수의 대리자로 평탄화 한 요소를 투영하는 (※ 단 평탄화 한 요소의 소속 업체와 함께) 그리고, 마지막으로 하나의 IEnumerable 된다. 라고도 말하는 것일까 요?

SelectMany (인수의 대리자 두)를 SelectMany (인수의 대리자 하나)에서 재작성

인수의 대리자가 하나의 오버로드를 사용하여이 예를 쓰는 아닌가하고 보았습니다. 수있었습니다.

먼저 원래 사람의 SelectMany (인수의 대리자 두)

LINQ에서 Character의 List에서 SkillView의 List를 작성 (재게)
List < SkillView >  skillViews  =  characters . SelectMany  ( 
        ( character  =>  character . Skills )  
        ( skillOwner ,  skill )  =>  new  SkillView {  OwnerName  =   skillOwner . Name ,  SkillName  =  skill . Name }) 
. ToList ();

다음 다시 쓴 것.

SelectMany (대리자 하나)과 Select로 재현
List < SkillView >  skillViews  =  characters 
    . SelectMany  ( character  =>  character . Skills . Select  ( 
        skill  =>  new  SkillView  {  OwnerName  =  character . Name ,  SkillName  =  skill . Name } 
    )) 
    . ToList ();

나는 인수 대리자를 두 가지고 사람의 작성을 좋아해요.

이 예제의 된장은 최종 성과물의 SkillView의 생성 Character과 Skill 모두 필요 것이라고 생각합니다. 대리자 두 SelectMany 오버로드는 각각의 대리자를 솔직하게 설명 할 수 있습니다. 하지만 대리자를 하나 인수에 취하는 오버로드는 하나의 대리자에서 반죽 해 돌리고있는 느낌을받습니다.

이런 경우는 인수 대리자를 두 가지고 사람의 작성을 채용할지 생각합니다.

SelectMany 오버로드에 대해

여기까지에서 SelectMany 오버로드를 두 소개했습니다.

  • 대리자를 1 개 취 (selector)
  • 대리자를 2 개 취 (collectionSelector과 resultSelectr)

입니다. 사실 SelectMany 오버로드는 네 개 있습니다. 그렇지만 크게 나누면 두 생각해도 좋을 것입니다. 지금까지 소개 한 두 오버로드 각각 인덱싱 된 처리가 실시 여부의 차이가 오버로드가 있습니다. Select 메서드의 인덱싱 된 오버로드와 비슷하네요. (이에 대해서는 이전 게시물했습니다. [C # LINQ] 인덱스로 프로젝션 (Select)로 추출 (Where) [i 싶어요!?] 다른 언어도 좀. )

2 개의 인수의 대리자의 차이] × 2 개, 인덱스의 차이] = 4 개]

총 4 개의 오버로드 네요.

정리

SelectMany 편리하네요! 목록의 목록을 평탄화 할 때 다른 foreach 문은 필요 없겠 네요. Xamarn에서 응용 프로그램 개발 및 Unity의 게임 개발에 활용할 수있는 장면이 많은 것이 아닐까요?

results matching ""

    No results matching ""