{ }codelper
高階関数P0318

基準の関数を受け取っていちばんの単語を選ぶ

単語が n 個あり、単語一つを数に変える基準が四つある。
len は単語の文字数である。
vowels は単語に含まれる母音 a, e, i, o, u の個数である。
kinds は単語に使われている異なる文字の種類の数である。
last は単語の最後の文字がアルファベットで何番目かである(a は 1、z は 26)。
単語のリストと基準の関数一つを受け取り、その基準で測った値がいちばん大きい単語を返す関数を一つだけ作り、四つの基準すべてに使う。
値がいちばん大きい単語が複数あれば、入力でいちばん先に出た単語を選ぶ。
一行目に n と質問の数 q が空白一つで区切って与えられる。
二行目に単語 n 個が空白一つで区切って与えられる。
続く q 行に基準の名前が一行に一つずつ与えられる。
質問ごとに、選んだ単語とその値を空白一つで区切って一行に出力せよ。

入力
1 ≤ n ≤ 100、1 ≤ q ≤ 100。
単語は英小文字だけからなる 1 文字以上 20 文字以下の文字列で、同じ単語が何度も出ることがある。
基準の名前は len、vowels、kinds、last のどれかである
出力
q 行:質問ごとに選んだ単語とその値
例 1 · stdin
6 4
kiwi banana fig queue papaya jazz
len
vowels
kinds
last
stdout
banana 6
queue 4
kiwi 3
jazz 26
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

関数を値のように別の関数へ渡す問題です。
渡すときは名前のあとに括弧を付けません。
Python には max(words, key=count_vowels) のように基準の関数を受け取る組み込み関数があり、C# にも words.MaxBy(key) があります。
どちらも値が同じなら先に出たものを返します。
関数を入れる型は言語ごとに違います:TS type Key = (w: string) => number、Java ToIntFunction<String>(メソッドは Main::countVowels で指し、key.applyAsInt(w) で呼びます)、C# Func<string, int>、C は関数ポインタ int (*key)(const char *)、C++ std::function<int(const std::string&)>。
Python・JS・Craykon は型を書かずにそのまま渡します。
名前のない関数(ラムダ)をその場で作る書き方も違います:Python lambda w: len(w)、JS・TS・C# w => w.length、Java w -> w.length()、C++ [](const std::string& w) { return (int)w.size(); }。
Craykon と C にはラムダがないので、名前のある関数を作って keys["len"] = 文字数 のように名前だけを書いて入れます。
Java Collections.max(words, Comparator.comparingInt(key)) と C++ std::max_element は比べる関数を受け取り、値が同じなら先に出たものを返します。
落とし穴は三つ:① ループで探すとき >= で比べると、値が同じ単語が出るたびに入れ替えるので最後の単語が残ります(例の len が papaya 6、kinds が jazz 3 に)。
> を使います。
② 並べ替えて最後を取る Python sorted(words, key=key)[-1] も、同じ値の中で最後の単語を返します。
並べ替えは値が同じものの元の順番を保つからです。
C++ std::minmax_element(...).second もいちばん大きいものの中の最後を指します。
③ JS で best(words, key(words[0])) のように括弧を付けると、関数ではなくその場で呼んだ結果(数)が渡ります。
best の中でその数を呼ぼうとして key is not a function のエラーで止まります。

Pythonmain.py · 27 行
def count_vowels(w):    return sum(1 for c in w if c in "aeiou")  def count_kinds(w):    return len(set(w))  def last_rank(w):    return ord(w[-1]) - ord("a") + 1  # 기준 이름으로 함수를 찾는 표. 이름 뒤에 괄호를 붙이지 않는다(부르지 않고 함수 자체를 담는다)KEYS = {"len": len, "vowels": count_vowels, "kinds": count_kinds, "last": last_rank}  def best(words, key):    # max는 key 함수로 잰 값이 가장 큰 것을 돌려주고, 값이 같으면 먼저 나온 것을 돌려준다    return max(words, key=key)  n, q = map(int, input().split())words = input().split()for _ in range(q):    key = KEYS[input().strip()]    w = best(words, key)    print(w, key(w))
JavaScriptmain.js · 28 行
const lines = require("fs").readFileSync(0, "utf8").split("\n"); // 기준 이름으로 함수를 찾는 표. 화살표 함수는 그대로 값이다const KEYS = {  len: (w) => w.length,  vowels: (w) => [...w].filter((c) => "aeiou".includes(c)).length,  kinds: (w) => new Set(w).size,  last: (w) => w.charCodeAt(w.length - 1) - "a".charCodeAt(0) + 1,}; // 낱말 목록과 기준 함수를 받는다. 더 클 때만(>) 바꾸므로 값이 같으면 먼저 나온 낱말이 남는다function best(words, key) {  let top = words[0];  for (const w of words) {    if (key(w) > key(top)) top = w;  }  return top;} const [n, q] = lines[0].trim().split(/\s+/).map(Number);const words = lines[1].trim().split(/\s+/);const out = [];for (let i = 0; i < q; i++) {  const key = KEYS[lines[2 + i].trim()];  const w = best(words, key);  out.push(w + " " + key(w));}console.log(out.join("\n"));
TypeScriptmain.ts · 32 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n"); // 함수의 타입: 낱말을 받아 수를 돌려준다type Key = (w: string) => number; // 기준 이름으로 함수를 찾는 표. 화살표 함수는 그대로 값이다const KEYS: Record<string, Key> = {  len: (w) => w.length,  vowels: (w) => [...w].filter((c) => "aeiou".includes(c)).length,  kinds: (w) => new Set(w).size,  last: (w) => w.charCodeAt(w.length - 1) - "a".charCodeAt(0) + 1,}; // 낱말 목록과 기준 함수를 받는다. 더 클 때만(>) 바꾸므로 값이 같으면 먼저 나온 낱말이 남는다function best(words: string[], key: Key): string {  let top: string = words[0];  for (const w of words) {    if (key(w) > key(top)) top = w;  }  return top;} const [n, q]: number[] = lines[0].trim().split(/\s+/).map(Number);const words: string[] = lines[1].trim().split(/\s+/);const out: string[] = [];for (let i = 0; i < q; i++) {  const key: Key = KEYS[lines[2 + i].trim()];  const w: string = best(words, key);  out.push(w + " " + key(w));}console.log(out.join("\n"));
JavaMain.java · 55 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.Arrays;import java.util.Collections;import java.util.Comparator;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.StringTokenizer;import java.util.function.ToIntFunction; public class Main {    static int countVowels(String w) {        int count = 0;        for (char c : w.toCharArray()) if ("aeiou".indexOf(c) >= 0) count++;        return count;    }     static int countKinds(String w) {        return (int) w.chars().distinct().count();    }     static int lastRank(String w) {        return w.charAt(w.length() - 1) - 'a' + 1;    }     // 낱말 목록과 기준 함수를 받는다. Collections.max는 값이 같으면 먼저 나온 것을 돌려준다    static String best(List<String> words, ToIntFunction<String> key) {        return Collections.max(words, Comparator.comparingInt(key));    }     public static void main(String[] args) throws IOException {        // 함수를 값으로 담는 타입이 ToIntFunction<String>이다. 메서드는 "클래스::이름"으로 가리킨다        Map<String, ToIntFunction<String>> keys = new HashMap<>();        keys.put("len", String::length);        keys.put("vowels", Main::countVowels);        keys.put("kinds", Main::countKinds);        keys.put("last", Main::lastRank);         BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(in.readLine());        int n = Integer.parseInt(st.nextToken());        int q = Integer.parseInt(st.nextToken());        List<String> words = Arrays.asList(in.readLine().trim().split(" +"));        StringBuilder out = new StringBuilder();        for (int i = 0; i < q; i++) {            ToIntFunction<String> key = keys.get(in.readLine().trim());            String w = best(words, key);            // 담아 둔 함수는 key(w)가 아니라 key.applyAsInt(w)로 부른다            out.append(w).append(' ').append(key.applyAsInt(w)).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 36 行
using System;using System.Collections.Generic;using System.Linq;using System.Text; class Program{    static int CountVowels(string w) => w.Count(c => "aeiou".Contains(c));    static int CountKinds(string w) => w.Distinct().Count();    static int LastRank(string w) => w[w.Length - 1] - 'a' + 1;     // 낱말 목록과 기준 함수를 받는다. MaxBy는 값이 같으면 먼저 나온 것을 돌려준다    static string Best(string[] words, Func<string, int> key) => words.MaxBy(key);     static void Main()    {        // 함수를 값으로 담는 타입이 Func<string, int>이다(string을 받아 int를 돌려주는 함수)        var keys = new Dictionary<string, Func<string, int>>        {            ["len"] = w => w.Length,            ["vowels"] = CountVowels,            ["kinds"] = CountKinds,            ["last"] = LastRank,        };        int q = int.Parse(Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries)[1]);        string[] words = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);        var sb = new StringBuilder();        for (int i = 0; i < q; i++)        {            Func<string, int> key = keys[Console.ReadLine().Trim()];            string best = Best(words, key);            sb.Append(best).Append(' ').Append(key(best)).Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 39 行
#include <algorithm>#include <functional>#include <iostream>#include <map>#include <set>#include <string>#include <vector> // 함수를 값으로 담는 타입: 낱말을 받아 int를 돌려주는 것이면 함수든 람다든 담긴다using Key = std::function<int(const std::string&)>; // 낱말 목록과 기준 함수를 받는다. max_element는 값이 같으면 먼저 나온 것을 가리킨다const std::string& best(const std::vector<std::string>& words, const Key& key) {    return *std::max_element(words.begin(), words.end(),                             [&](const std::string& a, const std::string& b) { return key(a) < key(b); });} int main() {    std::map<std::string, Key> keys = {        {"len", [](const std::string& w) { return (int)w.size(); }},        {"vowels", [](const std::string& w) {             return (int)std::count_if(w.begin(), w.end(), [](char c) { return std::string("aeiou").find(c) != std::string::npos; });         }},        {"kinds", [](const std::string& w) { return (int)std::set<char>(w.begin(), w.end()).size(); }},        {"last", [](const std::string& w) { return w.back() - 'a' + 1; }},    };     int n, q;    std::cin >> n >> q;    std::vector<std::string> words(n);    for (auto& w : words) std::cin >> w;    for (int i = 0; i < q; i++) {        std::string name;        std::cin >> name;        const Key& key = keys[name];        const std::string& w = best(words, key);        std::cout << w << ' ' << key(w) << '\n';    }}
Cmain.c · 61 行
#include <stdio.h>#include <string.h> static int length_of(const char *w) {    return (int)strlen(w);} static int count_vowels(const char *w) {    int count = 0;    for (; *w; w++) if (strchr("aeiou", *w)) count++;    return count;} static int count_kinds(const char *w) {    int seen[26] = {0}, count = 0;    for (; *w; w++) {        if (!seen[*w - 'a']) {            seen[*w - 'a'] = 1;            count++;        }    }    return count;} static int last_rank(const char *w) {    return w[strlen(w) - 1] - 'a' + 1;} /* 함수 포인터 타입: 낱말을 받아 int를 돌려주는 함수를 가리킨다 */typedef int (*key_fn)(const char *); /* 낱말 목록과 기준 함수를 받는다. 더 클 때만(>) 바꾸므로 값이 같으면 먼저 나온 낱말이 남는다 */static const char *best(char words[][21], int n, key_fn key) {    const char *top = words[0];    for (int i = 1; i < n; i++) {        if (key(words[i]) > key(top)) top = words[i];    }    return top;} int main(void) {    /* 기준 이름과 함수를 짝지은 표. 함수 이름만 쓰면 그 함수의 주소다 */    const char *names[] = {"len", "vowels", "kinds", "last"};    key_fn keys[] = {length_of, count_vowels, count_kinds, last_rank};     int n, q;    static char words[100][21];    scanf("%d %d", &n, &q);    for (int i = 0; i < n; i++) scanf("%20s", words[i]);    for (int i = 0; i < q; i++) {        char name[16];        scanf("%15s", name);        for (int k = 0; k < 4; k++) {            if (strcmp(name, names[k]) == 0) {                const char *w = best(words, n, keys[k]);                printf("%s %d\n", w, keys[k](w));            }        }    }    return 0;}
Craykonmain.cray · 64 行
// 기준 함수 네 개. 크레이콘에는 람다가 없어서 이름 있는 함수로 만든다글자수(w){    返す w.長さ;} 모음수(w){    count = 0;    それぞれ (c の中 w)    {        もし ("aeiou".含む(c))        {            count++;        }    }    返す count;} 가짓수(w){    본것 = 辞書();    それぞれ (c の中 w)    {        본것[c] = 真;    }    返す 본것.個数;} 끝글자순번(w){    返す 文字.コード(w[w.長さ - 1]) - 文字.コード("a") + 1;} // 낱말 목록과 기준 함수를 받는다. 더 클 때만(>) 바꾸므로 값이 같으면 먼저 나온 낱말이 남는다으뜸(낱말들, 기준){    top = 낱말들[0];    それぞれ (w の中 낱말들)    {        もし (기준(w) > 기준(top))        {            top = w;        }    }    返す top;} // 함수 이름을 괄호 없이 쓰면 함수 자체가 값으로 담긴다keys = 辞書();keys["len"] = 글자수;keys["vowels"] = 모음수;keys["kinds"] = 가짓수;keys["last"] = 끝글자순번; 첫줄 = コンソール.数読み();q = 첫줄[1];words = コンソール.行読み().空白除去().分ける();繰り返す (i = 0; i < q; i++){    key = keys[コンソール.行読み().空白除去()];    w = 으뜸(words, key);    コンソール.書く(w + " " + key(w));}