<commit_before>import _ from 'lodash';

/**
 * Count the number of values in the list that pass the predicate.
 */
export function count<T>(
  list: readonly T[],
  predicate: (value: T) => boolean | null | undefined
): number {
  return _.sumBy(list, (item) => (predicate(item) ? 1 : 0));
}

export function preventNaN<T extends number | string>(testValue: number, defaultValue: T) {
  return !isNaN(testValue) ? testValue : defaultValue;
}

/**
 * given the key 'key', turns
 * [           { key: '1' },      { key: '2' } ]
 * into { '1': { key: '1' }, '2': { key: '2' } }
 */
export function objectifyArray<T>(
  array: T[],
  key: keyof T | ((obj: T) => keyof T)
): NodeJS.Dict<T> {
  return array.reduce<NodeJS.Dict<T>>((acc, val) => {
    if (_.isFunction(key)) {
      acc[key(val) as string] = val;
    } else {
      const prop = val[key];
      if (typeof prop === 'string') {
        acc[prop] = val;
      } else if (Array.isArray(prop)) {
        for (const eachKeyName of prop) {
          acc[eachKeyName] = val;
        }
      } else {
        const keyName = JSON.stringify(prop);
        acc[keyName] = val;
      }
    }
    return acc;
  }, {});
}

/**
 * Produce a function that can memoize a calculation about an item. The cache is backed by
 * a WeakMap so when the item is garbage collected the cache is freed up too.
 */
export function weakMemoize<T extends object, R>(func: (arg0: T) => R): (arg1: T) => R {
  const cache = new WeakMap<T, R>();
  return (arg: T): R => {
    if (cache.has(arg)) {
      return cache.get(arg)!;
    }

    const value = func(arg);
    cache.set(arg, value);
    return value;
  };
}

/**
 * Transform an async function into a version that will only execute once at a time - if there's already
 * a version going, the existing promise will be returned instead of running it again.
 */
export function dedupePromise<T extends unknown[], K>(
  func: (...args: T) => Promise<K>
): (...args: T) => Promise<K> {
  let promiseCache: Promise<K> | null = null;
  return async (...args: T) => {
    if (promiseCache) {
      return promiseCache;
    }
    promiseCache = func(...args);
    try {
      return await promiseCache;
    } finally {
      promiseCache = null;
    }
  };
}

// setTimeout as a promise
export function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/** Copy a string to the clipboard */
export function copyString(str: string) {
  function listener(e: ClipboardEvent) {
    e.clipboardData?.setData('text/plain', str);
    e.preventDefault();
  }
  document.addEventListener('copy', listener);
  document.execCommand('copy');
  document.removeEventListener('copy', listener);
}

/** Download a string as a file */
export function download(data: string, filename: string, type: string) {
  const a = document.createElement('a');<commit_msg>simplify string copying<commit_after>import _ from 'lodash';

/**
 * Count the number of values in the list that pass the predicate.
 */
export function count<T>(
  list: readonly T[],
  predicate: (value: T) => boolean | null | undefined
): number {
  return _.sumBy(list, (item) => (predicate(item) ? 1 : 0));
}

export function preventNaN<T extends number | string>(testValue: number, defaultValue: T) {
  return !isNaN(testValue) ? testValue : defaultValue;
}

/**
 * given the key 'key', turns
 * [           { key: '1' },      { key: '2' } ]
 * into { '1': { key: '1' }, '2': { key: '2' } }
 */
export function objectifyArray<T>(
  array: T[],
  key: keyof T | ((obj: T) => keyof T)
): NodeJS.Dict<T> {
  return array.reduce<NodeJS.Dict<T>>((acc, val) => {
    if (_.isFunction(key)) {
      acc[key(val) as string] = val;
    } else {
      const prop = val[key];
      if (typeof prop === 'string') {
        acc[prop] = val;
      } else if (Array.isArray(prop)) {
        for (const eachKeyName of prop) {
          acc[eachKeyName] = val;
        }
      } else {
        const keyName = JSON.stringify(prop);
        acc[keyName] = val;
      }
    }
    return acc;
  }, {});
}

/**
 * Produce a function that can memoize a calculation about an item. The cache is backed by
 * a WeakMap so when the item is garbage collected the cache is freed up too.
 */
export function weakMemoize<T extends object, R>(func: (arg0: T) => R): (arg1: T) => R {
  const cache = new WeakMap<T, R>();
  return (arg: T): R => {
    if (cache.has(arg)) {
      return cache.get(arg)!;
    }

    const value = func(arg);
    cache.set(arg, value);
    return value;
  };
}

/**
 * Transform an async function into a version that will only execute once at a time - if there's already
 * a version going, the existing promise will be returned instead of running it again.
 */
export function dedupePromise<T extends unknown[], K>(
  func: (...args: T) => Promise<K>
): (...args: T) => Promise<K> {
  let promiseCache: Promise<K> | null = null;
  return async (...args: T) => {
    if (promiseCache) {
      return promiseCache;
    }
    promiseCache = func(...args);
    try {
      return await promiseCache;
    } finally {
      promiseCache = null;
    }
  };
}

// setTimeout as a promise
export function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/** Copy a string to the clipboard */
export function copyString(str: string) {
  navigator.clipboard.writeText(str);
}

/** Download a string as a file */
export function download(data: string, filename: string, type: string) {
  const a = document.createElement('a');
{'fraction_non_alphanumeric': 0.15258855585831063, 'fraction_numerical': 0.0036330608537693005, 'mean_word_length': 2.932857142857143, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7352811', 'n_tokens_mistral': 1770, 'n_tokens_neox': 1762, 'n_words': 579}