---
sidebar_position: 2
---

import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'

# Recipes

:::tip

No recipe for what you're trying to do?
[File an issue](https://github.com/TomerAberbach/lfi/issues/new?title=[Recipe]%20)
or
[update the documentation](https://github.com/TomerAberbach/lfi/tree/main/website/docs/recipes.mdx)!

:::

## How do I group values by a key?

If each key is associated with exactly one value, then you can use a
[keyed reducer](/docs/concepts/reducer#keyedreducer-and-asynckeyedreducer):

<Tabs groupId='protocol' queryString>
<TabItem value='sync' label='Sync'>

```js playground
import { map, pipe, reduce, toMap } from 'lfi'

console.log(
  pipe(
    [`sloth`, `lazy`, `sleep`],
    // Create an iterable of key-value pairs
    map(word => [word, word.length]),
    // Collect the key-value pairs into a map
    reduce(toMap()),
  ),
)
//=> Map(3) {
//=>   'sloth' => 5,
//=>   'lazy' => 4,
//=>   'sleep' => 5
//=> }
```

</TabItem>
<TabItem value='async' label='Async'>

```js playground
import { asAsync, mapAsync, pipe, reduceAsync, toMap } from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`

console.log(
  await pipe(
    asAsync([`sloth`, `lazy`, `sleep`]),
    // Create an async iterable of key-value pairs
    mapAsync(async word => {
      const response = await fetch(`${API_URL}/${word}`)
      const [{ phonetic }] = await response.json()
      return [word, phonetic]
    }),
    // Collect the key-value pairs into a map
    reduceAsync(toMap()),
  ),
)
//=> Map(3) {
//=>   'sloth' => '/slɑθ/',
//=>   'lazy' => '/ˈleɪzi/',
//=>   'sleep' => '/sliːp/'
//=> }
```

</TabItem>
<TabItem value='concur' label='Concur'>

```js playground
import { asConcur, mapConcur, pipe, reduceConcur, toMap } from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`

console.log(
  await pipe(
    asConcur([`sloth`, `lazy`, `sleep`]),
    // Create a concur iterable of key-value pairs
    mapConcur(async word => {
      const response = await fetch(`${API_URL}/${word}`)
      const [{ phonetic }] = await response.json()
      return [word, phonetic]
    }),
    // Collect the key-value pairs into a map
    reduceConcur(toMap()),
  ),
)
// NOTE: This order may change between runs
//=> Map(3) {
//=>   'sloth' => '/slɑθ/',
//=>   'lazy' => '/ˈleɪzi/',
//=>   'sleep' => '/sliːp/'
//=> }
```

</TabItem>
</Tabs>

:::warning

If there are multiple key-value pairs with the same key, then only the last
key-value pair is preserved.

:::

If each key is associated with one or more values, then you can use
[`toGrouped`](/docs/concepts/reducer#togrouped):

<Tabs groupId='protocol' queryString>
<TabItem value='sync' label='Sync'>

```js playground
import { map, pipe, reduce, toArray, toGrouped, toMap } from 'lfi'

console.log(
  pipe(
    [`sloth`, `lazy`, `sleep`],
    // Create an iterable of key-value pairs
    map(word => [word.length, word]),
    // Collect the values by key into a map where each group is an array
    reduce(toGrouped(toArray(), toMap())),
  ),
)
//=> Map(2) {
//=>  5 => [ 'sloth', 'sleep' ],
//=>  4 => [ 'lazy' ]
//=> }
```

</TabItem>
<TabItem value='async' label='Async'>

```js playground
import {
  asAsync,
  mapAsync,
  pipe,
  reduceAsync,
  toArray,
  toGrouped,
  toMap,
} from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`

console.log(
  await pipe(
    asAsync([`sloth`, `lazy`, `sleep`]),
    // Create an async iterable of key-value pairs
    mapAsync(async word => {
      const response = await fetch(`${API_URL}/${word}`)
      const [{ meanings }] = await response.json()
      return [meanings[0].partOfSpeech, word]
    }),
    // Collect the values by key into a map where each group is an array
    reduceAsync(toGrouped(toArray(), toMap())),
  ),
)
//=> Map(2) {
//=>  'noun' => [ 'sloth', 'lazy' ],
//=>  'verb' => [ 'sleep' ]
//=> }
```

</TabItem>
<TabItem value='concur' label='Concur'>

```js playground
import {
  asConcur,
  mapConcur,
  pipe,
  reduceConcur,
  toArray,
  toGrouped,
  toMap,
} from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`

console.log(
  await pipe(
    asConcur([`sloth`, `lazy`, `sleep`]),
    // Create an async iterable of key-value pairs
    mapConcur(async word => {
      const response = await fetch(`${API_URL}/${word}`)
      const [{ meanings }] = await response.json()
      return [meanings[0].partOfSpeech, word]
    }),
    // Collect the values by key into a map where each group is an array
    reduceConcur(toGrouped(toArray(), toMap())),
  ),
)
// NOTE: This order may change between runs
//=> Map(2) {
//=>  'noun' => [ 'sloth', 'lazy' ],
//=>  'verb' => [ 'sleep' ]
//=> }
```

</TabItem>
</Tabs>

In both cases you can swap out both `toArray` and `toMap` for other reducers to
collect the key-value pairs into different data structures.

## How do I limit the concurrency of a concur iterable?

You can wrap the async callback you want to limit the concurrency of with
[`limit-concur`](https://www.npmjs.com/package/limit-concur):

```js playground
import { asConcur, mapConcur, pipe, reduceConcur, toArray } from 'lfi'
import limitConcur from 'limit-concur'

const API_URL = `https://random-word-form.herokuapp.com/random/adjective`

let pendingRequests = 0
console.log(
  await pipe(
    asConcur([`strawberry`, `max`, `bitsy`, `tommy`]),
    mapConcur(
      // At most 2 requests at a time
      limitConcur(2, async sloth => {
        console.log(++pendingRequests)
        const [adjective] = await (await fetch(API_URL)).json()
        console.log(--pendingRequests)
        return `${adjective} ${sloth}`
      }),
    ),
    reduceConcur(toArray()),
  ),
)
//=> 1
//=> 2
//=> 1
//=> 2
//=> 1
//=> 2
//=> 1
//=> 0
// NOTE: This order may change between runs
//=> [
//=>   'kind strawberry',
//=>   'humble max',
//=>   'great bitsy',
//=>   'beautiful tommy'
//=> ]
```

## How do I reduce an iterable to more than one result in one pass?

You can use [`toMultiple`](/docs/concepts/reducer#tomultiple):

<Tabs groupId='protocol' queryString>
<TabItem value='sync' label='Sync'>

```js playground
import { map, pipe, reduce, toCount, toJoin, toMultiple, toSet } from 'lfi'

console.log(
  pipe(
    [`sloth`, `lazy`, `sleep`],
    map(word => word.length),
    reduce(toMultiple([toSet(), toCount(), toJoin(`,`)])),
  ),
)
//=> [
//=>   Set(2) { 5, 4 },
//=>   3,
//=>   '5,4,5'
//=> ]

console.log(
  pipe(
    [`sloth`, `lazy`, `sleep`],
    map(word => word.length),
    reduce(
      toMultiple({
        set: toSet(),
        count: toCount(),
        string: toJoin(`,`),
      }),
    ),
  ),
)
//=> {
//=>   set: Set(2) { 5, 4 },
//=>   count: 3,
//=>   string: '5,4,5'
//=> }
```

</TabItem>
<TabItem value='async' label='Async'>

```js playground
import {
  asAsync,
  flatMapAsync,
  pipe,
  reduceAsync,
  toCount,
  toJoin,
  toMultiple,
  toSet,
} from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`
const getPartsOfSpeech = async word => {
  const response = await fetch(`${API_URL}/${word}`)
  const [{ meanings }] = await response.json()
  return meanings.map(meaning => meaning.partOfSpeech)
}

console.log(
  await pipe(
    asAsync([`sloth`, `lazy`, `sleep`]),
    flatMapAsync(getPartsOfSpeech),
    reduceAsync(toMultiple([toSet(), toCount(), toJoin(`,`)])),
  ),
)
//=> [
//=>   Set(3) { 'noun', 'verb', 'adjective' },
//=>   6,
//=>   'noun,verb,noun,verb,adjective,verb'
//=> ]

console.log(
  await pipe(
    asAsync([`sloth`, `lazy`, `sleep`]),
    flatMapAsync(getPartsOfSpeech),
    reduceAsync(
      toMultiple({
        set: toSet(),
        count: toCount(),
        string: toJoin(`,`),
      }),
    ),
  ),
)
//=> {
//=>   set: Set(3) { 'noun', 'verb', 'adjective' },
//=>   count: 6,
//=>   string: 'noun,verb,noun,verb,adjective,verb'
//=> }
```

</TabItem>
<TabItem value='concur' label='Concur'>

```js playground
import {
  asConcur,
  mapConcur,
  pipe,
  reduceConcur,
  toCount,
  toJoin,
  toMultiple,
  toSet,
} from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`
const getPartsOfSpeech = async word => {
  const response = await fetch(`${API_URL}/${word}`)
  const [{ meanings }] = await response.json()
  return meanings.map(meaning => meaning.partOfSpeech)
}

console.log(
  await pipe(
    asConcur([`sloth`, `lazy`, `sleep`]),
    mapConcur(getPartsOfSpeech),
    reduceConcur(toMultiple([toSet(), toCount(), toJoin(`,`)])),
  ),
)
// NOTE: This order may change between runs
//=> [
//=>   Set(3) { 'noun', 'verb', 'adjective' },
//=>   6,
//=>   'noun,verb,noun,verb,adjective,verb'
//=> ]

console.log(
  await pipe(
    asConcur([`sloth`, `lazy`, `sleep`]),
    mapConcur(getPartsOfSpeech),
    reduceConcur(
      toMultiple({
        set: toSet(),
        count: toCount(),
        string: toJoin(`,`),
      }),
    ),
  ),
)
// NOTE: This order may change between runs
//=> {
//=>   set: Set(3) { 'noun', 'verb', 'adjective' },
//=>   count: 6,
//=>   string: 'noun,verb,noun,verb,adjective,verb'
//=> }
```

</TabItem>
</Tabs>