オブジェクトの配列を扱った練習問題 5選 in Typescript (Javascriptでも可)

# はじめに
みなさん、こんにちは **torihazi**です

雨がすごくて飛びそうです。

今日は最近実務で扱うことの多くなったオブジェクトの配列について

もっと触っていかないとダメだなと思ったので、練習問題作ってみてやってみました

10個だと多すぎるし、3個だと少なすぎるかなと思ったので

間をとって5個にしました

5個くらいならやってもいいかなってなったので。

解答お待ちしてます。

元の配列を変えないことやできるだけ汎用的になるように頑張ってください

あと実務1ヶ月目なので、そこは察してください。

# 実行環境
docker 使ってます。 調べたら山のように出てくると思うので、各自にお任せします。

# 1問
```
type Student = {
  id: number;
  name: string;
  grades: number[];
};

const students: Student[] = [
  { id: 1, name: "田中", grades: [85, 90, 78] },
  { id: 2, name: "鈴木", grades: [92, 86, 98] },
  { id: 3, name: "佐藤", grades: [76, 88, 82] },
];

// 課題: 各学生の平均点を計算し、平均点が85点以上の学生の名前を配列で返す関数を作成してください。

答え　[ '鈴木' ]

```

<details><summary>解答</summary>

```typescript:pr1.ts
type Student = {
  id: number;
  name: string;
  grades: number[];
  averages?: number; <= 勝手に追加しました
};
const filterStudentsByAverageGrade = (
  threshold: number,
  students: Student[]
): string[] => {
  if (!students || students.length === 0) {
    return [];
  }

  return students
    .map((student) => ({
      ...student,
      avarages:
        student.grades.reduce((acc, cur) => acc + cur) / student.grades.length,
    }))
    .filter((student) => student.avarages >= threshold)
    .map((student) => student.name);
};

console.log(filterStudentsByAverageGrade(85, students));
```

```typescript: 別解
こっちならaveragesを型に追加しなくていい

const getHighPerformingStudents = (
  threshold: number,
  students: Student[]
): string[] => {
  if (!students || students.length === 0) {
    return [];
  }

  return students.reduce((acc: string[], student) => {
    const average = student.grades.reduce((sum, grade) => sum + grade, 0) / student.grades.length;
    if (average >= threshold) {
      acc.push(student.name);
    }
    return acc;
  }, []);
};
```

</details>

# 2問
```typescript
type Product = {
  id: string;
  name: string;
  category: string;
  stock: number;
};

const inventory: Product[][] = [
  [
    { id: "A1", name: "Tシャツ", category: "衣類", stock: 50 },
    { id: "A2", name: "ジーンズ", category: "衣類", stock: 30 },
  ],
  [
    { id: "B1", name: "スマートフォン", category: "電子機器", stock: 20 },
    { id: "B2", name: "ノートPC", category: "電子機器", stock: 15 },
  ],
];

// 課題: カテゴリごとの在庫数を合計し、{ category: string, totalStock: number }の形式のオブジェクトの配列を返す関数を作成してください。

答え 
[
  { category: '衣類', totalStock: 80 },
  { category: '電子機器', totalStock: 35 }
]
```

<details><summary>解答</summary>

```typescript:pr2.ts
type CategoryStock = Pick<Product, "category"> & {
  totalStock: number;
};

const getCategoryStock = (inventory: Product[][]): CategoryStock[] => {
  const flatInventory: Product[] = inventory.flat();
  const uniqueCategories = new Set(flatInventory.map((item) => item.category));

  return Array.from(uniqueCategories).map((category) => {
    const totalStock = flatInventory
      .filter((item) => item.category === category)
      .reduce((arr, item) => arr + item.stock, 0);

    return { category, totalStock };
  });
};

console.log(getCategoryStock(inventory));

```

</details>

# 3問
```typescript
type Employee = {
  id: number;
  name: string;
};

type Project = {
  id: string;
  name: string;
  members: Employee[];
};

const projects: Project[] = [
  {
    id: "P1",
    name: "Webサイトリニューアル",
    members: [
      { id: 1, name: "山田" },
      { id: 2, name: "伊藤" },
    ],
  },
  {
    id: "P2",
    name: "モバイルアプリ開発",
    members: [
      { id: 2, name: "伊藤" },
      { id: 3, name: "中村" },
    ],
  },
];

// 課題: 複数のプロジェクトに参加している従業員の名前を配列で返す関数を作成してください。
答え　[ '伊藤' ]
```

<details><summary>解答</summary>

```typescript:pr3.ts

function findDuplicates<T>(arr: T[]): T[] {
  const seen = new Set<T>();
  const duplicates = new Set<T>();

  for (const item of arr) {
    if (seen.has(item)) {
      duplicates.add(item);
    } else {
      seen.add(item);
    }
  }

  return Array.from(duplicates);
}

const findEmployeesInMultipleProjects = (projects: Project[]): string[] => {
  const allMemberNames = projects
    // .map((project) => project.members)
    // .flat()
    // .map((member) => member.name);
    .flatMap((project) => project.members.map((member) => member.name));

  return findDuplicates(allMemberNames);
};

console.log(findEmployeesInMultipleProjects(projects));
```

</details>

# 4問

```typescript
type MenuItem = {
  id: string;
  name: string;
  price: number;
};

type Order = {
  tableNumber: number;
  items: MenuItem[];
};

const orders: Order[] = [
  {
    tableNumber: 1,
    items: [
      { id: "M1", name: "パスタ", price: 1200 },
      { id: "M2", name: "サラダ", price: 500 },
    ],
  },
  {
    tableNumber: 2,
    items: [
      { id: "M3", name: "ステーキ", price: 2000 },
      { id: "M4", name: "スープ", price: 400 },
      { id: "M2", name: "サラダ", price: 500 },
    ],
  },
];

// 課題: 各テーブルの注文合計金額を計算し、{ tableNumber: number, totalAmount: number }の形式のオブジェクトの配列を返す関数を作成してください。
答え
[
  { tableNumber: 1, totalAmount: 1700 },
  { tableNumber: 2, totalAmount: 2900 }
]
```

<details><summary>解答</summary>

```typescript:pr4.ts
type CalculateTableTotalsType = { tableNumber: number; totalAmount: number };
const calculateTableTotals = (orders: Order[]): CalculateTableTotalsType[] =>
  orders.map((order) => ({
    tableNumber: order.tableNumber,
    totalAmount: order.items.reduce((arr, item) => arr + item.price, 0),
  }));

console.log(calculateTableTotals(orders));

```

</details>

# 5問
```typescript
type Book = {
  id: string;
  title: string;
  author: string;
  genres: string[];
};

const library: Book[][] = [
  [
    {
      id: "B1",
      title: "1984",
      author: "George Orwell",
      genres: ["SF", "ディストピア"],
    },
    {
      id: "B2",
      title: "Pride and Prejudice",
      author: "Jane Austen",
      genres: ["ロマンス", "古典"],
    },
  ],
  [
    {
      id: "B3",
      title: "The Catcher in the Rye",
      author: "J.D. Salinger",
      genres: ["小説", "青春"],
    },
    {
      id: "B4",
      title: "To Kill a Mockingbird",
      author: "Harper Lee",
      genres: ["小説", "社会問題"],
    },
  ],
];

// 課題: 指定されたジャンルに属する本のタイトルを全て含む配列を返す関数を作成してください。同じ本が複数回含まれないようにしてください。
//例えば ["小説", "ロマンス"]で渡した結果です。

[
  'Pride and Prejudice',
  'The Catcher in the Rye',
  'To Kill a Mockingbird'
]

```

<details><summary>解答</summary>

```typescript:pr5.ts
const getUniqueBookTitlesByGenre = (genres: string[]): string[] => {
  if (genres.length === 0 || library.length === 0) {
    return [];
  }

  const flatLibaray = library.flat();
  const uniqueBookTitles = flatLibaray
    .filter((book) => genres.some((genre) => book.genres.includes(genre)))
    .map((book) => book.title);
  return [...new Set(uniqueBookTitles)];
};

console.log(getUniqueBookTitlesByGenre(["小説", "ロマンス"]));
```

</details>

# 終わりに
今回はこの問題文のようにどのようなものを作ればよいかが言語化されていたので

大方の方向性はわかりました。

しかし実務ではこんな言語化された文章で仕様なんて伝えられません。

自分で作るべき仕様を言語化して "問題文"を作らなければなりません。

そこにも難しさはあると思いました。

技術的な要素ではflat と mapを一緒にするようなflatmapや Setを使って重複削除するやり方は初めて知りました。

Javascript Primer等を読んで Setの存在は知っていましたが、使い道が

不明だったのでこれを機に知れてよかったです。

あとは5問目に登場したタグ検索のようなものの実装方法です。

タグが1つだけならfilterとincludesで行けるかと思うのですが、

複数指定となるとこの方法なのかということも新しい発見です。

これはOR検索になるんですかね。ANDならeveryとかですかね

ちなみにサンプルデータや問題とかは Claudeに作ってもらってます。

コードレビューもClaudeです。

まだパフォーマンス等を意識できていないので実用性があるかと言われたら、

疑問ですがそこはおいおいやっていきます。それにまだわかりません。

別解お待ちしてます。