Como classificar uma lista no Scala por dois campos?

101

como classificar uma lista no Scala por dois campos, neste exemplo irei classificar por lastName e firstName?

case class Row(var firstName: String, var lastName: String, var city: String)

var rows = List(new Row("Oscar", "Wilde", "London"),
                new Row("Otto",  "Swift", "Berlin"),
                new Row("Carl",  "Swift", "Paris"),
                new Row("Hans",  "Swift", "Dublin"),
                new Row("Hugo",  "Swift", "Sligo"))

rows.sortBy(_.lastName)

Eu tento coisas assim

rows.sortBy(_.lastName + _.firstName)

mas não funciona. Portanto, estou curioso por uma solução boa e fácil.

Twistleton
fonte

Respostas:

216
rows.sortBy(r => (r.lastName, r.firstName))
Senia
fonte
4
e se quisermos reverter a classificação em lastName e, em seguida, a classificação natural em firstName?
Sachin K de
14
@SachinK: você tem que criar o seu próprio Orderingpor Rowclasse e usá-lo com sortedmétodo como este: rows.sorted(customOrdering). Você também pode usar costume Orderingpara Tuple2assim: rows.sortBy(r => (r.lastName, r.firstName))( Ordering.Tuple2(Ordering.String.reverse, Ordering.String) ).
Senia
5
@SachinK: Você poderia implementar customOrderingcomo Ordering[Row]manualmente ou usar Ordering.byassim: val customOrdering = Ordering.by ((r: Row) => (r.lastName, r.firstName)) (Ordering.Tuple2 (Ordering.String.reverse, Ordering.String)) `
Senia
1
Excelente. Ou para classificar em ordem decrescenterows.sortBy(r => (-r.field1, -r.field2))
Brent Faust
@BrentFaust você não pode usar -com String. Você deve usar Ordering::reversedesta forma: rows.sortBy(r => (r.lastName, r.firstName))(implicitly[Ordering[(String, String)]].reverse).
Senia
12
rows.sortBy (row => row.lastName + row.firstName)

Se você quiser classificar pelos nomes mesclados, como na sua pergunta, ou

rows.sortBy (row => (row.lastName, row.firstName))

se você deseja classificar primeiro por lastName, então firstName; relevante para nomes mais longos (Wild, Wilder, Wilderman).

Se você escrever

rows.sortBy(_.lastName + _.firstName)

com 2 sublinhados, o método espera dois parâmetros:

<console>:14: error: wrong number of parameters; expected = 1
       rows.sortBy (_.lastName + _.firstName)
                               ^
Usuário desconhecido
fonte
1
A ordem disso provavelmente não será a mesma da classificação por nome e sobrenome.
Marcin,
1
Especificamente, quando os sobrenomes têm comprimentos diferentes
Luigi Plinge,
7

Em geral, se você usar um algoritmo de classificação estável, poderá classificar por uma chave e depois pela próxima.

rows.sortBy(_.firstName).sortBy(_.lastName)

O resultado final será classificado pelo sobrenome e, onde for igual, pelo nome.

Marcin
fonte
Tem certeza de que o scala sortByusa classificação estável? Caso contrário, esta resposta não tem sentido.
om-nom-nom
1
@ om-nom-nom: scala-lang.org/api/current/scala/util/Sorting$.html quickSort é definido apenas para tipos de valor, então sim.
Marcin,
1
rows é uma lista imutável e sortBy retorna um novo valor em vez de modificar aquele no qual funciona (mesmo em classes mutáveis). Portanto, sua segunda expressão é apenas classificar a lista original não classificada.
Luigi Plinge,
3
Scala, sob o capô do método sortBy, usa java.util.Arrays.sort, que para a matriz de objetos garante ser estável. Então, sim, essa solução está correta. (Isso foi verificado em Scala 2.10)
Marcin Pieciukiewicz
1
É interessante pensar sobre o desempenho disso em comparação com um único sortBy que cria uma tupla. Com essa abordagem, você obviamente não precisa criar essas tuplas, mas com a abordagem de tupla você só precisa comparar os primeiros nomes onde os sobrenomes correspondem. Mas suponho que não importa - se você está escrevendo um código crítico de desempenho, não deveria usar sortBy de forma alguma!
AmigoNico
-3

Talvez isso funcione apenas para uma lista de tuplas, mas

scala> var zz = List((1, 0.1), (2, 0.5), (3, 0.6), (4, 0.3), (5, 0.1))
zz: List[(Int, Double)] = List((1,0.1), (2,0.5), (3,0.6), (4,0.3), (5,0.1))

scala> zz.sortBy( x => (-x._2, x._1))
res54: List[(Int, Double)] = List((3,0.6), (2,0.5), (4,0.3), (1,0.1), (5,0.1))

parece funcionar e ser uma forma simples de expressá-lo.

spreinhardt
fonte
Mas não funciona para strings, que é o que o OP está classificando.
O arquetípico Paulo
Esta questão já possui várias respostas bem recebidas que não se limitam a listas de tuplas. Então, qual é a razão de postar?
buzina de
@honk: As soluções anteriores realmente não funcionam (AFAICT) em uma lista de tuplas. Se eu não fosse um novato no Scala, talvez entendesse como transformar essas soluções anteriores para funcionar nesse caso, mas hoje não sei. Achei que minha resposta poderia ajudar outro novato do Scala a fazer a mesma coisa que eu estava tentando fazer.
spreinhardt
@ user3508605: Agradeço sua vontade de contribuir. No entanto, a ideia do Stack Overflow é ter perguntas com problemas específicos (como é o caso aqui) e respostas que abordem esses problemas específicos (e apenas esses). Sua resposta fornece uma solução para um problema diferente. Portanto, este é o lugar errado para postá-lo. Se você acha que sua resposta é valiosa, faça uma nova pergunta. Descreva o problema correspondente na nova pergunta e, em seguida, poste sua resposta lá. Finalmente, não se esqueça de remover sua resposta aqui. Obrigado pela sua cooperação!
buzina em
@honk: Claro, vou mover minha resposta para outra pergunta. E, se eu pudesse impor a você o acréscimo de um comentário à resposta anterior a esta pergunta (de Marcin), parece estar simplesmente errado. (Não tenho pontos de credibilidade suficientes para poder postar nele.) O exemplo nessa resposta apenas classifica primeiro por uma chave e depois classifica novamente por uma chave diferente, eliminando efetivamente os resultados da primeira classificação. Pelo menos em uma lista de tuplas sim.
spreinhardt